mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-23 11:44:06 -08:00
feat(core): Introduce DB health check (#10661)
This commit is contained in:
parent
3a8078068e
commit
a8e80d0c4b
|
@ -119,11 +119,17 @@ export abstract class AbstractServer {
|
|||
protected setupPushServer() {}
|
||||
|
||||
private async setupHealthCheck() {
|
||||
// health check should not care about DB connections
|
||||
// main health check should not care about DB connections
|
||||
this.app.get('/healthz', async (_req, res) => {
|
||||
res.send({ status: 'ok' });
|
||||
});
|
||||
|
||||
this.app.get('/healthz/readiness', async (_req, res) => {
|
||||
return Db.connectionState.connected && Db.connectionState.migrated
|
||||
? res.status(200).send({ status: 'ok' })
|
||||
: res.status(503).send({ status: 'error' });
|
||||
});
|
||||
|
||||
const { connectionState } = Db;
|
||||
this.app.use((_req, res, next) => {
|
||||
if (connectionState.connected) {
|
||||
|
|
|
@ -172,6 +172,12 @@ export class Worker extends BaseCommand {
|
|||
|
||||
const server = http.createServer(app);
|
||||
|
||||
app.get('/healthz/readiness', async (_req, res) => {
|
||||
return Db.connectionState.connected && Db.connectionState.migrated
|
||||
? res.status(200).send({ status: 'ok' })
|
||||
: res.status(503).send({ status: 'error' });
|
||||
});
|
||||
|
||||
app.get(
|
||||
'/healthz',
|
||||
|
||||
|
|
22
packages/cli/test/integration/healthcheck.controller.test.ts
Normal file
22
packages/cli/test/integration/healthcheck.controller.test.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import * as testDb from './shared/test-db';
|
||||
import { setupTestServer } from '@test-integration/utils';
|
||||
|
||||
const testServer = setupTestServer({ endpointGroups: ['health'] });
|
||||
|
||||
describe('HealthcheckController', () => {
|
||||
it('should return ok when DB is connected and migrated', async () => {
|
||||
const response = await testServer.restlessAgent.get('/healthz/readiness');
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toEqual({ status: 'ok' });
|
||||
});
|
||||
|
||||
it('should return error when DB is not connected', async () => {
|
||||
await testDb.terminate();
|
||||
|
||||
const response = await testServer.restlessAgent.get('/healthz/readiness');
|
||||
|
||||
expect(response.statusCode).toBe(503);
|
||||
expect(response.body).toEqual({ status: 'error' });
|
||||
});
|
||||
});
|
|
@ -39,11 +39,16 @@ export async function init() {
|
|||
await Db.migrate();
|
||||
}
|
||||
|
||||
export function isReady() {
|
||||
return Db.connectionState.connected && Db.connectionState.migrated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop test DB, closing bootstrap connection if existing.
|
||||
*/
|
||||
export async function terminate() {
|
||||
await Db.close();
|
||||
Db.connectionState.connected = false;
|
||||
}
|
||||
|
||||
// Can't use `Object.keys(entities)` here because some entities have a `Entity` suffix, while the repositories don't
|
||||
|
|
|
@ -10,6 +10,7 @@ import type { LicenseMocker } from './license';
|
|||
import type { Project } from '@/databases/entities/project';
|
||||
|
||||
type EndpointGroup =
|
||||
| 'health'
|
||||
| 'me'
|
||||
| 'users'
|
||||
| 'auth'
|
||||
|
@ -54,6 +55,7 @@ export interface TestServer {
|
|||
authAgentFor: (user: User) => TestAgent;
|
||||
publicApiAgentFor: (user: User) => TestAgent;
|
||||
authlessAgent: TestAgent;
|
||||
restlessAgent: TestAgent;
|
||||
license: LicenseMocker;
|
||||
}
|
||||
|
||||
|
|
|
@ -44,9 +44,16 @@ function prefix(pathSegment: string) {
|
|||
}
|
||||
|
||||
const browserId = 'test-browser-id';
|
||||
function createAgent(app: express.Application, options?: { auth: boolean; user: User }) {
|
||||
function createAgent(
|
||||
app: express.Application,
|
||||
options?: { auth: boolean; user?: User; noRest?: boolean },
|
||||
) {
|
||||
const agent = request.agent(app);
|
||||
void agent.use(prefix(REST_PATH_SEGMENT));
|
||||
|
||||
const withRestSegment = !options?.noRest;
|
||||
|
||||
if (withRestSegment) void agent.use(prefix(REST_PATH_SEGMENT));
|
||||
|
||||
if (options?.auth && options?.user) {
|
||||
const token = Container.get(AuthService).issueJWT(options.user, browserId);
|
||||
agent.jar.setCookie(`${AUTH_COOKIE_NAME}=${token}`);
|
||||
|
@ -89,6 +96,7 @@ export const setupTestServer = ({
|
|||
httpServer: app.listen(0),
|
||||
authAgentFor: (user: User) => createAgent(app, { auth: true, user }),
|
||||
authlessAgent: createAgent(app),
|
||||
restlessAgent: createAgent(app, { auth: false, noRest: true }),
|
||||
publicApiAgentFor: (user) => publicApiAgent(app, { user }),
|
||||
license: new LicenseMocker(),
|
||||
};
|
||||
|
@ -119,6 +127,13 @@ export const setupTestServer = ({
|
|||
app.use(...apiRouters);
|
||||
}
|
||||
|
||||
if (endpointGroups?.includes('health')) {
|
||||
app.get('/healthz/readiness', async (_req, res) => {
|
||||
testDb.isReady()
|
||||
? res.status(200).send({ status: 'ok' })
|
||||
: res.status(503).send({ status: 'error' });
|
||||
});
|
||||
}
|
||||
if (endpointGroups.length) {
|
||||
for (const group of endpointGroups) {
|
||||
switch (group) {
|
||||
|
|
Loading…
Reference in a new issue