Skip to content

Commit

Permalink
Add readiness check support and documentation (#114)
Browse files Browse the repository at this point in the history
  • Loading branch information
adusumillipraveen authored Jun 2, 2020
1 parent 203a752 commit 7c44079
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 6 deletions.
56 changes: 53 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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", {
Expand All @@ -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.
Expand Down
1 change: 1 addition & 0 deletions healthcheck/install.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
28 changes: 27 additions & 1 deletion healthcheck/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
17 changes: 16 additions & 1 deletion test/unit/installTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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()
})
})
})
37 changes: 37 additions & 0 deletions test/unit/routesTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down

0 comments on commit 7c44079

Please sign in to comment.