backend-app-api: move healthchecks to healthService

Signed-off-by: Vincenzo Scamporlino <vincenzos@spotify.com>
This commit is contained in:
Vincenzo Scamporlino
2024-05-17 15:17:26 +02:00
parent 53ced701dd
commit 8d4735767d
11 changed files with 206 additions and 7 deletions
@@ -0,0 +1,81 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { mockServices } from '@backstage/backend-test-utils';
import request from 'supertest';
import { createHealthRouter } from './createHealthRouter';
import express from 'express';
describe('createHealthRouter', () => {
describe('readiness', () => {
it(`should return a 500 response if the server hasn't started yet`, async () => {
const hc = createHealthRouter({
lifecycle: mockServices.rootLifecycle.mock(),
});
const app = express().use(hc);
const response = await request(app).get('/v1/readiness');
expect(response.status).toBe(500);
});
it('should return 200 if the server has started', async () => {
const lifecycle = mockServices.rootLifecycle.mock();
let mockServerStartedFn = () => {};
lifecycle.addStartupHook.mockImplementation(
fn => (mockServerStartedFn = fn),
);
const hc = createHealthRouter({ lifecycle });
const app = express().use(hc);
mockServerStartedFn();
const response = await request(app).get('/v1/readiness').expect(200);
expect(response.body).toEqual({ status: 'ok' });
});
it(`should return a 500 response if the server has stopped`, async () => {
const lifecycle = mockServices.rootLifecycle.mock();
let mockServerStartedFn = () => {};
let mockServerStoppedFn = () => {};
lifecycle.addStartupHook.mockImplementation(
fn => (mockServerStartedFn = fn),
);
lifecycle.addShutdownHook.mockImplementation(
fn => (mockServerStoppedFn = fn),
);
const hc = createHealthRouter({ lifecycle });
const app = express().use(hc);
mockServerStartedFn();
mockServerStoppedFn();
const response = await request(app).get('/v1/readiness');
expect(response.status).toBe(500);
});
});
describe('liveness', () => {
it('should return 200 if the server has started', async () => {
const lifecycle = mockServices.rootLifecycle.mock();
const hc = createHealthRouter({ lifecycle });
const app = express().use(hc);
const response = await request(app).get('/v1/liveness').expect(200);
expect(response.body).toEqual({ status: 'ok' });
});
});
});
@@ -0,0 +1,46 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Router from 'express-promise-router';
import { Request, Response } from 'express';
import { RootLifecycleService } from '@backstage/backend-plugin-api';
export function createHealthRouter(options: {
lifecycle: RootLifecycleService;
}) {
const router = Router();
let isRunning = false;
options.lifecycle.addStartupHook(() => {
isRunning = true;
});
options.lifecycle.addShutdownHook(() => {
isRunning = false;
});
router.get('/v1/readiness', async (_request: Request, response: Response) => {
if (!isRunning) {
throw new Error('Backend has not started yet');
}
response.json({ status: 'ok' });
});
router.get('/v1/liveness', async (_request: Request, response: Response) => {
response.json({ status: 'ok' });
});
return router;
}
@@ -0,0 +1,34 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
coreServices,
createServiceFactory,
} from '@backstage/backend-plugin-api';
import { createHealthRouter } from './createHealthRouter';
export const healthServiceFactory = createServiceFactory({
service: coreServices.health,
deps: {
rootHttpRouter: coreServices.rootHttpRouter,
lifecycle: coreServices.rootLifecycle,
},
async factory({ lifecycle, rootHttpRouter }) {
rootHttpRouter.use('.backstage/health', createHealthRouter({ lifecycle }));
return {};
},
});
@@ -0,0 +1,17 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { healthServiceFactory } from './healthServiceFactory';
@@ -0,0 +1,20 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @public
*/
export interface HealthService {}
@@ -26,11 +26,6 @@ export interface HttpRouterServiceAuthPolicy {
allow: 'unauthenticated' | 'user-cookie';
}
/** @public */
export interface HttpRouterHealthCheckConfig {
handler: () => Promise<any>;
}
/**
* Allows plugins to register HTTP routes.
*
@@ -102,6 +102,13 @@ export namespace coreServices {
import('./DiscoveryService').DiscoveryService
>({ id: 'core.discovery' });
/**
* The service reference for the plugin scoped {@link RootHealthService}.
*/
export const health = createServiceRef<
import('./RootHealthService').RootHealthService
>({ id: 'core.health', scope: 'root' });
/**
* Authentication of HTTP requests.
*
@@ -32,10 +32,10 @@ export type {
export type { RootConfigService } from './RootConfigService';
export type { DatabaseService } from './DatabaseService';
export type { DiscoveryService } from './DiscoveryService';
export type { HealthService } from './HealthService';
export type {
HttpRouterService,
HttpRouterServiceAuthPolicy,
HttpRouterHealthCheckConfig,
} from './HttpRouterService';
export type { HttpAuthService } from './HttpAuthService';
export type {
@@ -348,7 +348,6 @@ export namespace mockServices {
export const factory = httpRouterServiceFactory;
export const mock = simpleMock(coreServices.httpRouter, () => ({
use: jest.fn(),
healthCheckConfig: jest.fn(),
addAuthPolicy: jest.fn(),
}));
}