Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Redis info to health/info page #402

Merged
merged 8 commits into from
Sep 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,10 @@ AWS_ACCESS_KEY_ID=uploadaccesskey
AWS_SECRET_ACCESS_KEY=uploadsecretkey
AWS_MAINTENANCE_BUCKET=upload-bucket-gov-uk

# Redis config
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=

# Feature flags
ENABLE_REISSUING_BILLING_BATCHES=false
18 changes: 15 additions & 3 deletions app/services/health/info.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@
const ChildProcess = require('child_process')
const util = require('util')
const exec = util.promisify(ChildProcess.exec)
const redis = require('@redis/client')

const ChargingModuleRequestLib = require('../../lib/charging-module-request.lib.js')
const RequestLib = require('../../lib/request.lib.js')
const LegacyRequestLib = require('../../lib/legacy-request.lib.js')

const redisConfig = require('../../../config/redis.config.js')
const servicesConfig = require('../../../config/services.config.js')

/**
Expand Down Expand Up @@ -62,10 +64,20 @@ async function _getVirusScannerData () {

async function _getRedisConnectivityData () {
try {
const { stdout, stderr } = await exec('redis-server --version')
return stderr ? `ERROR: ${stderr}` : stdout
const client = redis.createClient({
socket: {
host: redisConfig.host,
port: redisConfig.port
},
password: redisConfig.password
})

await client.connect()
await client.disconnect()

return 'Up and running'
} catch (error) {
return `ERROR: ${error.message}`
return 'Error connecting to Redis'
}
}

Expand Down
18 changes: 18 additions & 0 deletions config/redis.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use strict'

/**
* Config values used to connect to Redis
* @module RedisConfig
*/

// We require dotenv directly in each config file to support unit tests that depend on this this subset of config.
// Requiring dotenv in multiple places has no effect on the app when running for real.
require('dotenv').config()

const config = {
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT,
password: process.env.REDIS_PASSWORD
}

module.exports = config
50 changes: 50 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@hapi/hapi": "^21.3.2",
"@hapi/inert": "^7.1.0",
"@hapi/vision": "^7.0.2",
"@redis/client": "^1.5.9",
"@smithy/node-http-handler": "^2.0.4",
"bcryptjs": "^2.4.3",
"blipp": "^4.0.2",
Expand Down
88 changes: 64 additions & 24 deletions test/services/health/info.service.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const servicesConfig = require('../../../config/services.config.js')
// Things we need to stub
const ChargingModuleRequestLib = require('../../../app/lib/charging-module-request.lib.js')
const LegacyRequestLib = require('../../../app/lib/legacy-request.lib.js')
const redis = require('@redis/client')
const RequestLib = require('../../../app/lib/request.lib.js')

// Thing under test
Expand All @@ -40,11 +41,13 @@ describe('Info service', () => {
let chargingModuleRequestLibStub
let legacyRequestLibStub
let requestLibStub
let redisStub

beforeEach(() => {
chargingModuleRequestLibStub = Sinon.stub(ChargingModuleRequestLib, 'get')
legacyRequestLibStub = Sinon.stub(LegacyRequestLib, 'get')
requestLibStub = Sinon.stub(RequestLib, 'get')
redisStub = Sinon.stub(redis, 'createClient')

// These requests will remain unchanged throughout the tests. We do alter the ones to the AddressFacade and the
// water-api (foreground-service) though, which is why they are defined separately in each test.
Expand All @@ -61,6 +64,8 @@ describe('Info service', () => {
chargingModuleRequestLibStub
.withArgs('status')
.resolves(goodRequestResults.chargingModule)

// redisStub.returns({ connect: Sinon.fake().resolves(), disconnect: Sinon.fake().resolves() })
})

afterEach(() => {
Expand Down Expand Up @@ -88,14 +93,10 @@ describe('Info service', () => {
stdout: 'ClamAV 9.99.9/26685/Mon Oct 10 08:00:01 2022\n',
stderror: null
})
execStub
.withArgs('redis-server --version')
.resolves({
stdout: 'Redis server v=9.99.9 sha=00000000:0 malloc=jemalloc-5.2.1 bits=64 build=66bd629f924ac924\n',
stderror: null
})
const utilStub = { promisify: Sinon.stub().callsFake(() => execStub) }
InfoService = Proxyquire('../../../app/services/health/info.service', { util: utilStub })

redisStub.returns({ connect: Sinon.stub().resolves(), disconnect: Sinon.stub().resolves() })
})

it('returns details on each', async () => {
Expand All @@ -106,16 +107,62 @@ describe('Info service', () => {
])

expect(result.appData).to.have.length(10)

expect(result.virusScannerData).to.equal('ClamAV 9.99.9/26685/Mon Oct 10 08:00:01 2022\n')
expect(result.redisConnectivityData).to.equal('Up and running')
})
})

describe('when a service we check via the shell', () => {
describe('when Redis', () => {
beforeEach(async () => {
// In these scenarios everything is hunky-dory so we return 2xx responses from these services
requestLibStub
.withArgs(`${servicesConfig.addressFacade.url}/address-service/hola`)
.resolves(goodRequestResults.addressFacade)
legacyRequestLibStub.withArgs('water', 'health/info', false).resolves(goodRequestResults.app)

const execStub = Sinon
.stub()
.withArgs('clamdscan --version')
.resolves({
stdout: 'ClamAV 9.99.9/26685/Mon Oct 10 08:00:01 2022\n',
stderror: null
})
const utilStub = { promisify: Sinon.stub().callsFake(() => execStub) }
InfoService = Proxyquire('../../../app/services/health/info.service', { util: utilStub })
})

describe('is not running', () => {
beforeEach(async () => {
redisStub.returns({
connect: Sinon.stub().throwsException(new Error('Redis check went boom')),
disconnect: Sinon.stub().resolves()
})
})

it('handles the error and still returns a result for the other services', async () => {
const result = await InfoService.go()

expect(result).to.include([
'virusScannerData', 'redisConnectivityData', 'addressFacadeData', 'chargingModuleData', 'appData'
])
expect(result.appData).to.have.length(10)

expect(result.virusScannerData).to.equal('ClamAV 9.99.9/26685/Mon Oct 10 08:00:01 2022\n')
expect(result.redisConnectivityData).to.equal('Error connecting to Redis')
})
})
})

describe('when ClamAV', () => {
beforeEach(async () => {
// In these scenarios everything is hunky-dory so we return 2xx responses from these services
requestLibStub
.withArgs(`${servicesConfig.addressFacade.url}/address-service/hola`)
.resolves(goodRequestResults.addressFacade)
legacyRequestLibStub.withArgs('water', 'health/info', false).resolves(goodRequestResults.app)

redisStub.returns({ connect: Sinon.stub().resolves(), disconnect: Sinon.stub().resolves() })
})

describe('is not running', () => {
Expand All @@ -129,12 +176,6 @@ describe('Info service', () => {
stdout: null,
stderr: 'Could not connect to clamd'
})
execStub
.withArgs('redis-server --version')
.resolves({
stdout: null,
stderr: 'Could not connect to Redis'
})
const utilStub = { promisify: Sinon.stub().callsFake(() => execStub) }
InfoService = Proxyquire('../../../app/services/health/info.service', { util: utilStub })
})
Expand All @@ -148,7 +189,7 @@ describe('Info service', () => {
expect(result.appData).to.have.length(10)

expect(result.virusScannerData).to.startWith('ERROR:')
expect(result.redisConnectivityData).to.startWith('ERROR:')
expect(result.redisConnectivityData).to.equal('Up and running')
})
})

Expand All @@ -160,9 +201,6 @@ describe('Info service', () => {
.stub()
.withArgs('clamdscan --version')
.throwsException(new Error('ClamAV check went boom'))
execStub
.withArgs('redis-server --version')
.throwsException(new Error('Redis check went boom'))
const utilStub = { promisify: Sinon.stub().callsFake(() => execStub) }
InfoService = Proxyquire('../../../app/services/health/info.service', { util: utilStub })
})
Expand All @@ -176,7 +214,7 @@ describe('Info service', () => {
expect(result.appData).to.have.length(10)

expect(result.virusScannerData).to.startWith('ERROR:')
expect(result.redisConnectivityData).to.startWith('ERROR:')
expect(result.redisConnectivityData).to.equal('Up and running')
})
})
})
Expand All @@ -191,14 +229,10 @@ describe('Info service', () => {
stdout: 'ClamAV 9.99.9/26685/Mon Oct 10 08:00:01 2022\n',
stderror: null
})
execStub
.withArgs('redis-server --version')
.resolves({
stdout: 'Redis server v=9.99.9 sha=00000000:0 malloc=jemalloc-5.2.1 bits=64 build=66bd629f924ac924\n',
stderror: null
})
const utilStub = { promisify: Sinon.stub().callsFake(() => execStub) }
InfoService = Proxyquire('../../../app/services/health/info.service', { util: utilStub })

redisStub.returns({ connect: Sinon.stub().resolves(), disconnect: Sinon.stub().resolves() })
})

describe('cannot be reached because of a network error', () => {
Expand All @@ -221,6 +255,9 @@ describe('Info service', () => {

expect(result.addressFacadeData).to.startWith('ERROR:')
expect(result.appData[0].version).to.startWith('ERROR:')

expect(result.virusScannerData).to.equal('ClamAV 9.99.9/26685/Mon Oct 10 08:00:01 2022\n')
expect(result.redisConnectivityData).to.equal('Up and running')
})
})

Expand All @@ -244,6 +281,9 @@ describe('Info service', () => {

expect(result.addressFacadeData).to.startWith('ERROR:')
expect(result.appData[0].version).to.startWith('ERROR:')

expect(result.virusScannerData).to.equal('ClamAV 9.99.9/26685/Mon Oct 10 08:00:01 2022\n')
expect(result.redisConnectivityData).to.equal('Up and running')
})
})
})
Expand Down