Skip to content

Commit

Permalink
Adds server component to set up master health check when using clusters.
Browse files Browse the repository at this point in the history
  • Loading branch information
Hector Virgen committed Aug 6, 2014
1 parent ffc1f93 commit 8597579
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 32 deletions.
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,19 @@ The health check responds with the following data:
"rss": 15208448,
"heapTotal": 7195904,
"heapUsed": 3183048
}
},
workers: []
}

Cluster Support:
----------------

If you're using the cluster module, a master health check can be created on its own port:

var health = require('tagged-health');
health.server({
path: '/health.json', // path to health check (default: /health.json)
port: 3000 // port to listen on (default: 3000)
}, function() {
console.log("Master health check listening on port 3000 at /health.json")
});
8 changes: 8 additions & 0 deletions demo/server.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
health = require '../lib'

options =
port: 3000
path: '/health.json'

health.server options, ->
console.log "Health check server is running on port 3000. Visit http://localhost:3000/health.json to view health."
3 changes: 2 additions & 1 deletion lib/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
require('coffee-script');
module.exports.middleware = require('./health_check');
module.exports.middleware = require('./middleware');
module.exports.server = require('./server');
5 changes: 3 additions & 2 deletions lib/health_check.coffee → lib/middleware.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ workersHealth = (workers) ->
# Middleware for connect that responds with a health check
# if the request path matches the provided path
middleware = (path, req, res, next) ->
return next() if req.path != path
return next() if req.url != path

id = if cluster.isWorker then cluster.worker.id else 'master'
data = health process, id
data.workers = workersHealth cluster.workers if cluster.isMaster

res.send data
res.writeHead 200, 'Content-Type': 'application/json'
res.end JSON.stringify data

# Returns a middleware function
# bound with the provided path.
Expand Down
7 changes: 7 additions & 0 deletions lib/server.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
connect = require 'connect'
middleware = require './middleware'

module.exports = (options = {}, done) ->
app = connect()
app.use middleware options.path
app.listen options.port or 3000, done
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tagged-health",
"version": "0.0.2",
"version": "0.0.3",
"description": "Health check middleware for connect",
"main": "lib/index.js",
"scripts": {
Expand Down Expand Up @@ -33,6 +33,7 @@
"sinon": "^1.10.3"
},
"dependencies": {
"coffee-script": "^1.7.1"
"coffee-script": "^1.7.1",
"connect": "^3.1.0"
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
healthCheck = require "#{LIB_DIR}/health_check"
healthCheck = require "#{LIB_DIR}/middleware"
cluster = require 'cluster'

describe 'healthCheck', ->
describe 'middleware', ->
beforeEach ->
@req = path: '/'
@res = send: sinon.spy()
@req = url: '/'
@res =
writeHead: sinon.spy()
end: sinon.spy()
@next = sinon.spy()

it 'is a function', ->
Expand All @@ -18,35 +20,37 @@ describe 'healthCheck', ->
beforeEach ->
@middleware = healthCheck() # use default options

it 'calls `next()` if `req.path` does not match default `/health.json`', ->
it 'calls `next()` if `req.url` does not match default `/health.json`', ->
@middleware @req, @res, @next
@next.called.should.be.true

it 'responds with health object if path matches default `/health.json`', ->
@req.path = '/health.json'
it 'responds with health object if `req.url` matches default `/health.json`', ->
@req.url = '/health.json'
@middleware @req, @res, @next
@res.send.calledOnce.should.be.true
@res.send.lastCall.args[0].should.be.an 'object'
@res.writeHead.calledOnce.should.be.true
@res.writeHead.lastCall.args[0].should.equal 200
@res.writeHead.lastCall.args[1].should.deep.equal 'Content-Type': 'application/json'

describe 'configurable route', ->
beforeEach ->
@middleware = healthCheck '/test/health.json' # configurable route

it 'responds with health object if path matches configured route', ->
@req.path = '/test/health.json'
it 'responds with health object if `req.url` matches configured url', ->
@req.url = '/test/health.json'
@middleware @req, @res, @next
@res.send.calledOnce.should.be.true
@res.send.lastCall.args[0].should.be.an 'object'
@res.writeHead.calledOnce.should.be.true
@res.writeHead.lastCall.args[0].should.equal 200
@res.writeHead.lastCall.args[1].should.deep.equal 'Content-Type': 'application/json'

it 'calls next() if path does not match default `/health.json`', ->
@req.path = '/health.json' # should not match, because the default was overwritten
it 'calls next() if `req.url` does not match default `/health.json`', ->
@req.url = '/health.json' # should not match, because the default was overwritten
@middleware @req, @res, @next
@next.called.should.be.true

describe 'health object', ->
beforeEach ->
@middleware = healthCheck() # use default options
@req.path = '/health.json' # ensure health object is returned
@req.url = '/health.json' # ensure health object is returned
sinon.stub(process, 'uptime').returns('uptime_canary')
sinon.stub(process, 'memoryUsage').returns('memory_canary');

Expand All @@ -56,27 +60,32 @@ describe 'healthCheck', ->

it 'contains `status` property set to "OK"', ->
@middleware @req, @res, @next
@res.send.lastCall.args[0].should.have.property 'status', 'OK'
body = JSON.parse @res.end.lastCall.args[0]
body.should.have.property 'status', 'OK'

it 'contains `arch` property set to `process.arch`', ->
@middleware @req, @res, @next
@res.send.lastCall.args[0].should.have.property 'arch', process.arch
body = JSON.parse @res.end.lastCall.args[0]
body.should.have.property 'arch', process.arch

it 'contains `pid` property set to `process.pid`', ->
@middleware @req, @res, @next
@res.send.lastCall.args[0].should.have.property 'pid', process.pid
body = JSON.parse @res.end.lastCall.args[0]
body.should.have.property 'pid', process.pid

it 'contains `uptime` property set to `process.uptime()`', ->
@middleware @req, @res, @next
@res.send.lastCall.args[0].should.have.property 'uptime', 'uptime_canary'
body = JSON.parse @res.end.lastCall.args[0]
body.should.have.property 'uptime', 'uptime_canary'

it 'contains `memory` property set to `process.memoryUsage()`', ->
@middleware @req, @res, @next
@res.send.lastCall.args[0].should.have.property 'memory', 'memory_canary'
body = JSON.parse @res.end.lastCall.args[0]
body.should.have.property 'memory', 'memory_canary'
describe 'cluster master', ->
beforeEach ->
@middleware = healthCheck() # use default options
@req.path = '/health.json' # ensure health object is returned
@req.url = '/health.json' # ensure health object is returned
sinon.stub(process, 'uptime').returns('uptime_canary')
sinon.stub(process, 'memoryUsage').returns('memory_canary');
# cluster.isMaster = true
Expand All @@ -99,17 +108,18 @@ describe 'healthCheck', ->

it 'contains health for each worker', ->
@middleware @req, @res, @next
@res.send.lastCall.args[0].should.have.property 'workers'
@res.send.lastCall.args[0].workers.should.be.an 'array'
@res.send.lastCall.args[0].workers.should.have.lengthOf 2
@res.send.lastCall.args[0].workers[0].should.contain
body = JSON.parse @res.end.lastCall.args[0]
body.should.have.property 'workers'
body.workers.should.be.an 'array'
body.workers.should.have.lengthOf 2
body.workers[0].should.contain
status: 'OK'
id: 'abc'
arch: 'abc_arch_canary'
pid: 'abc_pid_canary'
uptime: null # uptime is not available in child processes :(
memory: null # memory usage is not available in child processes :(
@res.send.lastCall.args[0].workers[1].should.contain
body.workers[1].should.contain
status: 'OK'
id: 'def'
arch: 'def_arch_canary'
Expand Down

0 comments on commit 8597579

Please sign in to comment.