refactor(core): Move multi-main state to InstanceSettings (#12144)

This commit is contained in:
Iván Ovejero 2024-12-11 18:25:22 +01:00 committed by GitHub
parent 77e2c75ca6
commit 28f1f6b561
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 96 additions and 103 deletions

View file

@ -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<InstanceSettings>({ instanceType: 'worker' }),
mock(),
mock(),
mock(),
mock<GlobalConfig>({ license: licenseConfig }),
);
await license.init();
@ -211,7 +210,6 @@ describe('License', () => {
mock<InstanceSettings>({ 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');

View file

@ -23,7 +23,7 @@ describe('WaitTracker', () => {
const executionRepository = mock<ExecutionRepository>();
const multiMainSetup = mock<MultiMainSetup>();
const orchestrationService = new OrchestrationService(mock(), multiMainSetup, mock());
const instanceSettings = mock<InstanceSettings>({ isLeader: true });
const instanceSettings = mock<InstanceSettings>({ isLeader: true, isMultiMain: false });
const project = mock<Project>({ id: 'projectId' });
const execution = mock<IExecutionResponse>({
@ -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<InstanceSettings>({ isLeader: false }),
mock<InstanceSettings>({ isLeader: false, isMultiMain: false }),
);
jest.spyOn(orchestrationService, 'isSingleMainSetup', 'get').mockReturnValue(false);
executionRepository.getWaitingExecutions.mockResolvedValue([]);

View file

@ -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) {

View file

@ -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 () => {

View file

@ -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.',
);
}
}

View file

@ -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<PushEvents> {
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<PushEvents> {
* 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 },

View file

@ -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<OrchestrationService>({ isMultiMainSetupEnabled: false });
const jobProcessor = mock<JobProcessor>();
let scalingService: ScalingService;
@ -82,7 +80,7 @@ describe('ScalingService', () => {
globalConfig,
mock(),
instanceSettings,
orchestrationService,
mock(),
mock(),
);

View file

@ -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
);
}

View file

@ -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');
}

View file

@ -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();

View file

@ -17,11 +17,10 @@ describe('PruningService', () => {
it('should start pruning on main instance that is the leader', () => {
const pruningService = new PruningService(
mockLogger(),
mock<InstanceSettings>({ isLeader: true }),
mock<InstanceSettings>({ isLeader: true, isMultiMain: true }),
mock(),
mock(),
mock<OrchestrationService>({
isMultiMainSetupEnabled: true,
multiMainSetup: mock<MultiMainSetup>(),
}),
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<InstanceSettings>({ isLeader: false }),
mock<InstanceSettings>({ isLeader: false, isMultiMain: true }),
mock(),
mock(),
mock<OrchestrationService>({
isMultiMainSetupEnabled: true,
multiMainSetup: mock<MultiMainSetup>(),
}),
mock(),
@ -55,11 +53,10 @@ describe('PruningService', () => {
it('should register leadership events if main on multi-main setup', () => {
const pruningService = new PruningService(
mockLogger(),
mock<InstanceSettings>({ isLeader: true }),
mock<InstanceSettings>({ isLeader: true, isMultiMain: true }),
mock(),
mock(),
mock<OrchestrationService>({
isMultiMainSetupEnabled: true,
multiMainSetup: mock<MultiMainSetup>({ 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<InstanceSettings>({ isLeader: true, instanceType: 'main' }),
mock<InstanceSettings>({ isLeader: true, instanceType: 'main', isMultiMain: true }),
mock(),
mock(),
mock<OrchestrationService>({
isMultiMainSetupEnabled: true,
multiMainSetup: mock<MultiMainSetup>(),
}),
mock<ExecutionsConfig>({ pruneData: true }),
@ -101,11 +97,10 @@ describe('PruningService', () => {
it('should return `false` based on config if leader main', () => {
const pruningService = new PruningService(
mockLogger(),
mock<InstanceSettings>({ isLeader: true, instanceType: 'main' }),
mock<InstanceSettings>({ isLeader: true, instanceType: 'main', isMultiMain: true }),
mock(),
mock(),
mock<OrchestrationService>({
isMultiMainSetupEnabled: true,
multiMainSetup: mock<MultiMainSetup>(),
}),
mock<ExecutionsConfig>({ 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<InstanceSettings>({ isLeader: false, instanceType: 'worker' }),
mock<InstanceSettings>({ isLeader: false, instanceType: 'worker', isMultiMain: true }),
mock(),
mock(),
mock<OrchestrationService>({
isMultiMainSetupEnabled: true,
multiMainSetup: mock<MultiMainSetup>(),
}),
mock<ExecutionsConfig>({ 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<InstanceSettings>({ isLeader: false, isFollower: true, instanceType: 'main' }),
mock<InstanceSettings>({
isLeader: false,
isFollower: true,
instanceType: 'main',
isMultiMain: true,
}),
mock(),
mock(),
mock<OrchestrationService>({
isMultiMainSetupEnabled: true,
multiMainSetup: mock<MultiMainSetup>(),
}),
mock<ExecutionsConfig>({ pruneData: true }),
@ -151,11 +149,10 @@ describe('PruningService', () => {
it('should not start pruning if service is disabled', () => {
const pruningService = new PruningService(
mockLogger(),
mock<InstanceSettings>({ isLeader: true, instanceType: 'main' }),
mock<InstanceSettings>({ isLeader: true, instanceType: 'main', isMultiMain: true }),
mock(),
mock(),
mock<OrchestrationService>({
isMultiMainSetupEnabled: true,
multiMainSetup: mock<MultiMainSetup>(),
}),
mock<ExecutionsConfig>({ 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<InstanceSettings>({ isLeader: true, instanceType: 'main' }),
mock<InstanceSettings>({ isLeader: true, instanceType: 'main', isMultiMain: true }),
mock(),
mock(),
mock<OrchestrationService>({
isMultiMainSetupEnabled: true,
multiMainSetup: mock<MultiMainSetup>(),
}),
mock<ExecutionsConfig>({ pruneData: true }),

View file

@ -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());
}

View file

@ -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());

View file

@ -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<CacheService>();
const registrations = new TestWebhookRegistrationsService(
cacheService,
mock<OrchestrationService>({ isMultiMainSetupEnabled: false }),
mock<InstanceSettings>({ isMultiMain: false }),
);
const registration = mock<TestWebhookRegistration>({

View file

@ -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

View file

@ -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)
) {

View file

@ -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);

View file

@ -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';
}