mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 04:04: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() {}
|
protected setupPushServer() {}
|
||||||
|
|
||||||
private async setupHealthCheck() {
|
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) => {
|
this.app.get('/healthz', async (_req, res) => {
|
||||||
res.send({ status: 'ok' });
|
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;
|
const { connectionState } = Db;
|
||||||
this.app.use((_req, res, next) => {
|
this.app.use((_req, res, next) => {
|
||||||
if (connectionState.connected) {
|
if (connectionState.connected) {
|
||||||
|
|
|
@ -172,6 +172,12 @@ export class Worker extends BaseCommand {
|
||||||
|
|
||||||
const server = http.createServer(app);
|
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(
|
app.get(
|
||||||
'/healthz',
|
'/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();
|
await Db.migrate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isReady() {
|
||||||
|
return Db.connectionState.connected && Db.connectionState.migrated;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Drop test DB, closing bootstrap connection if existing.
|
* Drop test DB, closing bootstrap connection if existing.
|
||||||
*/
|
*/
|
||||||
export async function terminate() {
|
export async function terminate() {
|
||||||
await Db.close();
|
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
|
// 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';
|
import type { Project } from '@/databases/entities/project';
|
||||||
|
|
||||||
type EndpointGroup =
|
type EndpointGroup =
|
||||||
|
| 'health'
|
||||||
| 'me'
|
| 'me'
|
||||||
| 'users'
|
| 'users'
|
||||||
| 'auth'
|
| 'auth'
|
||||||
|
@ -54,6 +55,7 @@ export interface TestServer {
|
||||||
authAgentFor: (user: User) => TestAgent;
|
authAgentFor: (user: User) => TestAgent;
|
||||||
publicApiAgentFor: (user: User) => TestAgent;
|
publicApiAgentFor: (user: User) => TestAgent;
|
||||||
authlessAgent: TestAgent;
|
authlessAgent: TestAgent;
|
||||||
|
restlessAgent: TestAgent;
|
||||||
license: LicenseMocker;
|
license: LicenseMocker;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,9 +44,16 @@ function prefix(pathSegment: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const browserId = 'test-browser-id';
|
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);
|
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) {
|
if (options?.auth && options?.user) {
|
||||||
const token = Container.get(AuthService).issueJWT(options.user, browserId);
|
const token = Container.get(AuthService).issueJWT(options.user, browserId);
|
||||||
agent.jar.setCookie(`${AUTH_COOKIE_NAME}=${token}`);
|
agent.jar.setCookie(`${AUTH_COOKIE_NAME}=${token}`);
|
||||||
|
@ -89,6 +96,7 @@ export const setupTestServer = ({
|
||||||
httpServer: app.listen(0),
|
httpServer: app.listen(0),
|
||||||
authAgentFor: (user: User) => createAgent(app, { auth: true, user }),
|
authAgentFor: (user: User) => createAgent(app, { auth: true, user }),
|
||||||
authlessAgent: createAgent(app),
|
authlessAgent: createAgent(app),
|
||||||
|
restlessAgent: createAgent(app, { auth: false, noRest: true }),
|
||||||
publicApiAgentFor: (user) => publicApiAgent(app, { user }),
|
publicApiAgentFor: (user) => publicApiAgent(app, { user }),
|
||||||
license: new LicenseMocker(),
|
license: new LicenseMocker(),
|
||||||
};
|
};
|
||||||
|
@ -119,6 +127,13 @@ export const setupTestServer = ({
|
||||||
app.use(...apiRouters);
|
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) {
|
if (endpointGroups.length) {
|
||||||
for (const group of endpointGroups) {
|
for (const group of endpointGroups) {
|
||||||
switch (group) {
|
switch (group) {
|
||||||
|
|
Loading…
Reference in a new issue