diff --git a/packages/cli/src/ActiveWorkflowRunner.ts b/packages/cli/src/ActiveWorkflowRunner.ts index 8ddc228a20..a665e794f3 100644 --- a/packages/cli/src/ActiveWorkflowRunner.ts +++ b/packages/cli/src/ActiveWorkflowRunner.ts @@ -108,6 +108,10 @@ export class ActiveWorkflowRunner implements IWebhookManager { await this.webhookService.populateCache(); } + async getAllWorkflowActivationErrors() { + return this.activationErrorsService.getAll(); + } + /** * Removes all the currently active workflows from memory. */ diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 8f3c29e6a7..6884e05706 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -120,6 +120,7 @@ import { CollaborationService } from './collaboration/collaboration.service'; import { RoleController } from './controllers/role.controller'; import { BadRequestError } from './errors/response-errors/bad-request.error'; import { NotFoundError } from './errors/response-errors/not-found.error'; +import { MultiMainSetup } from './services/orchestration/main/MultiMainSetup.ee'; import { PasswordUtility } from './services/password.utility'; const exec = promisify(callbackExec); @@ -307,6 +308,11 @@ export class Server extends AbstractServer { Container.get(RoleController), ]; + if (Container.get(MultiMainSetup).isEnabled) { + const { DebugController } = await import('./controllers/debug.controller'); + controllers.push(Container.get(DebugController)); + } + if (isLdapEnabled()) { const { service, sync } = LdapManager.getInstance(); controllers.push(new LdapController(service, sync, internalHooks)); diff --git a/packages/cli/src/controllers/debug.controller.ts b/packages/cli/src/controllers/debug.controller.ts new file mode 100644 index 0000000000..59829ee282 --- /dev/null +++ b/packages/cli/src/controllers/debug.controller.ts @@ -0,0 +1,35 @@ +import { Service } from 'typedi'; +import { Get, RestController } from '@/decorators'; +import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner'; +import { MultiMainSetup } from '@/services/orchestration/main/MultiMainSetup.ee'; +import { WorkflowRepository } from '@/databases/repositories/workflow.repository'; +import { In } from 'typeorm'; + +@RestController('/debug') +@Service() +export class DebugController { + constructor( + private readonly multiMainSetup: MultiMainSetup, + private readonly activeWorkflowRunner: ActiveWorkflowRunner, + private readonly workflowRepository: WorkflowRepository, + ) {} + + @Get('/multi-main-setup') + async getMultiMainSetupDetails() { + const leaderKey = await this.multiMainSetup.fetchLeaderKey(); + + const activeWorkflows = await this.workflowRepository.find({ + select: ['id', 'name'], + where: { id: In(this.activeWorkflowRunner.allActiveInMemory()) }, + }); + + const activationErrors = await this.activeWorkflowRunner.getAllWorkflowActivationErrors(); + + return { + instanceId: this.multiMainSetup.instanceId, + leaderKey, + activeWorkflows, + activationErrors, + }; + } +} diff --git a/packages/cli/src/services/orchestration/main/MultiMainSetup.ee.ts b/packages/cli/src/services/orchestration/main/MultiMainSetup.ee.ts index 2a23690906..b032b1e979 100644 --- a/packages/cli/src/services/orchestration/main/MultiMainSetup.ee.ts +++ b/packages/cli/src/services/orchestration/main/MultiMainSetup.ee.ts @@ -28,6 +28,10 @@ export class MultiMainSetup extends SingleMainSetup { return !this.isLeader; } + get instanceId() { + return this.id; + } + setLicensed(newState: boolean) { this.isLicensed = newState; } @@ -140,4 +144,8 @@ export class MultiMainSetup extends SingleMainSetup { payload, }); } + + async fetchLeaderKey() { + return this.redisPublisher.get(this.leaderKey); + } } diff --git a/packages/cli/test/integration/debug.controller.test.ts b/packages/cli/test/integration/debug.controller.test.ts new file mode 100644 index 0000000000..954d17da9c --- /dev/null +++ b/packages/cli/test/integration/debug.controller.test.ts @@ -0,0 +1,48 @@ +import { WorkflowRepository } from '@/databases/repositories/workflow.repository'; +import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner'; +import { mockInstance } from '../shared/mocking'; +import { randomName } from './shared/random'; +import { generateNanoId } from '@/databases/utils/generators'; +import type { WorkflowEntity } from '@/databases/entities/WorkflowEntity'; +import { setupTestServer } from './shared/utils'; +import type { SuperAgentTest } from 'supertest'; +import { createOwner } from './shared/db/users'; +import { MultiMainSetup } from '@/services/orchestration/main/MultiMainSetup.ee'; + +describe('DebugController', () => { + const workflowRepository = mockInstance(WorkflowRepository); + const activeWorkflowRunner = mockInstance(ActiveWorkflowRunner); + + let testServer = setupTestServer({ endpointGroups: ['debug'] }); + let ownerAgent: SuperAgentTest; + + beforeAll(async () => { + const owner = await createOwner(); + ownerAgent = testServer.authAgentFor(owner); + testServer.license.enable('feat:multipleMainInstances'); + }); + + describe('GET /debug/multi-main-setup', () => { + test('should return multi-main setup details', async () => { + const workflowId = generateNanoId(); + const activeWorkflows = [{ id: workflowId, name: randomName() }] as WorkflowEntity[]; + const activationErrors = { [workflowId]: 'Failed to activate' }; + const instanceId = 'main-71JdWtq306epIFki'; + + workflowRepository.find.mockResolvedValue(activeWorkflows); + activeWorkflowRunner.allActiveInMemory.mockReturnValue([workflowId]); + activeWorkflowRunner.getAllWorkflowActivationErrors.mockResolvedValue(activationErrors); + jest.spyOn(MultiMainSetup.prototype, 'instanceId', 'get').mockReturnValue(instanceId); + jest.spyOn(MultiMainSetup.prototype, 'fetchLeaderKey').mockResolvedValue('some-leader-key'); + + const response = await ownerAgent.get('/debug/multi-main-setup').expect(200); + + expect(response.body.data).toMatchObject({ + instanceId, + leaderKey: 'some-leader-key', + activeWorkflows, + activationErrors, + }); + }); + }); +}); diff --git a/packages/cli/test/integration/shared/types.ts b/packages/cli/test/integration/shared/types.ts index ff93f11632..6670fc1ff9 100644 --- a/packages/cli/test/integration/shared/types.ts +++ b/packages/cli/test/integration/shared/types.ts @@ -32,7 +32,8 @@ type EndpointGroup = | 'workflowHistory' | 'binaryData' | 'role' - | 'invitations'; + | 'invitations' + | 'debug'; export interface SetupProps { applyAuth?: boolean; diff --git a/packages/cli/test/integration/shared/utils/testServer.ts b/packages/cli/test/integration/shared/utils/testServer.ts index 5a76bec24c..351e0413e2 100644 --- a/packages/cli/test/integration/shared/utils/testServer.ts +++ b/packages/cli/test/integration/shared/utils/testServer.ts @@ -312,6 +312,11 @@ export const setupTestServer = ({ const { RoleController } = await import('@/controllers/role.controller'); registerController(app, config, Container.get(RoleController)); break; + + case 'debug': + const { DebugController } = await import('@/controllers/debug.controller'); + registerController(app, config, Container.get(DebugController)); + break; } } }