diff --git a/README.md b/README.md index 7cd985c..b13f607 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,42 @@ A library for adding reform standard compliant healthchecks to nodejs applications. +It exposes 3 endpoints: + +1. `/health` - Returns 200 by default along with `buildInfo`, can optionally include result evaluating all `checks` passed in config. +2. `/health/livness` - Returns 200 always. +3. `/health/readiness` - Returns 200 by default , can optionally include result evaluating all `readinessChecks` passed in config. + ## Usage Configure an express.js handler with checks. ```javascript -const healthcheck = require('@hmcts/nodejs-healthcheck'); +const config = { + checks: { + mySimpleWebCheck: healthcheck.web("https://example.com/status"), + myComplexWebCheck: healthcheck.web("https://example.com/other", { + callback: (err, res) => { + return res.body.status == "good" ? healthcheck.up() : healthcheck.down() + }, + timeout: 5000, + deadline: 10000, + }), + myRawCheck: healthcheck.raw(() => { + return myInternalCheck() ? healthcheck.up() : healthcheck.down() + }) + }, + buildInfo: { + myCustomBuildInfo: "yay" + } +}; +healthcheck.addTo(app, config); +``` -app.get("/status", healthcheck.configure({ +You can optionally include [readiness checks](#what-to-include-in-readiness-checks). + +```javascript +const config = { checks: { mySimpleWebCheck: healthcheck.web("https://example.com/status"), myComplexWebCheck: healthcheck.web("https://example.com/other", { @@ -25,12 +53,34 @@ app.get("/status", healthcheck.configure({ return myInternalCheck() ? healthcheck.up() : healthcheck.down() }) }, + readinessChecks: { + mySimpleWebCheck: healthcheck.web("https://example.com/status") + }, buildInfo: { myCustomBuildInfo: "yay" } -})); +}; +healthcheck.addTo(app, config); ``` +## what to include in readiness checks + +- On Kubernetes, readiness probes will be called periodically throughout the lifetime of the container. Container will be made temporarily unavailable from serving traffic when the readiness check fails. +- The requests won't even reach your application to handle errors. So, it is very important to consider what checks should be included into readiness probe. +- While adding all dependant services to readiness check can help in identifying any misconfiguration during startup, it could cause unwanted downtime for the application. +- K8s introduced startUp Probes (Alpha in 1.16 ) to handle startup cases separately. + +Based on above, you should include a dependency into readiness checks only if they are exclusive/hard dependencies for your service. Unavailability of soft dependencies needs to be handled in code to give appropriate customer experience. + +Good example for check to be included in readiness: + +- A private cache / database like `Redis` or `Elastic Search` which are exclusive to the application (not shared). + +Bad example for check to be included in readiness: + +- Any shared components like IDAM, S2S or CCD. + + ## Publishing Bump the version (SemVer) and create a release in the GitHub UI, Travis CI will then build test and release to the npm registry. diff --git a/healthcheck/install.js b/healthcheck/install.js index d784bcc..4d13654 100644 --- a/healthcheck/install.js +++ b/healthcheck/install.js @@ -6,6 +6,7 @@ const outputs = require('./outputs') function addTo (app, config) { app.get('/health', routes.configure(config)) app.get('/health/liveness', (req, res) => res.json(outputs.status(outputs.UP))) + app.get('/health/readiness', routes.checkReadiness(config.readinessChecks)) } module.exports = { diff --git a/healthcheck/routes.js b/healthcheck/routes.js index 8568b74..75ca0d8 100644 --- a/healthcheck/routes.js +++ b/healthcheck/routes.js @@ -54,7 +54,33 @@ function configure (config) { } } +function checkReadiness (readinessChecks = {}) { + const check = new checks.CompositeCheck(readinessChecks) + + return (req, res) => { + return Promise + .resolve(check.call(req, res)) + .then((results) => { + const allOk = Object.values(results) + .every((result) => result.status === outputs.UP) + const output = Object.assign( + outputs.status(allOk), + results + ) + const status = allOk ? 200 : 500 + if (!allOk) { + const downHealthChecks = Object.values(results) + .filter((result) => result.status === outputs.DOWN) + + logger.error('Health check failed, result for down endpoints: ', JSON.stringify(downHealthChecks)) + } + res.status(status).json(output) + }) + } +} + module.exports = { getBuildInfo: getBuildInfo, - configure: configure + configure: configure, + checkReadiness: checkReadiness } diff --git a/package.json b/package.json index 89d1a4b..64176f2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hmcts/nodejs-healthcheck", - "version": "1.6.0", + "version": "1.7.0", "description": "Healthcheck endpoint for Reform nodejs applications", "main": "index.js", "engines": { diff --git a/test/unit/installTest.js b/test/unit/installTest.js index 9d4e2d4..b8cddfd 100644 --- a/test/unit/installTest.js +++ b/test/unit/installTest.js @@ -32,7 +32,7 @@ describe('Testing liveness', function () { }) }) -describe('Testing Readiness for 200 OK', function () { +describe('Testing health for 200 OK', function () { it('should return 200 OK', function (done) { request(app) .get('/health') @@ -46,3 +46,18 @@ describe('Testing Readiness for 200 OK', function () { }) }) }) + +describe('Testing readiness for 200 OK', function () { + it('should return 200 OK', function (done) { + request(app) + .get('/health/readiness') + .expect(200) + .end((err, res) => { + if (err) { + return done(err) + } + expect(res.body.status).to.be.equal('UP') + done() + }) + }) +}) diff --git a/test/unit/routesTest.js b/test/unit/routesTest.js index 7c6c574..059b4a5 100644 --- a/test/unit/routesTest.js +++ b/test/unit/routesTest.js @@ -121,6 +121,43 @@ describe('Routes', () => { return route(req, res) }) + it('should return 200 and UP if readiness check is undefined', () => { + const route = routes.checkReadiness() + const [req, res] = makeReqRes(200, { + status: 'UP' + }) + + return route(req, res) + }) + + it('should return 200 OK if all checks pass', () => { + const route = routes.checkReadiness({ + check1: makeCheck(true), + check2: makeCheck(true) + }) + const [req, res] = makeReqRes(200, { + status: outputs.UP, + check1: { status: 'UP' }, + check2: { status: 'UP' } + }) + + return route(req, res) + }) + + it('should return 500 DOWN if any readiness checks fail', () => { + const route = routes.checkReadiness({ + check1: makeCheck(false), + check2: makeCheck(true) + }) + const [req, res] = makeReqRes(500, { + status: 'DOWN', + check1: { status: 'DOWN' }, + check2: { status: 'UP' } + }) + + return route(req, res) + }) + it('should return the extra build info', () => { const route = routes.configure({ checks: {