Merge pull request #240 from CartoDB/health-check

Add healthcheck endpoint
This commit is contained in:
Raul Ochoa 2015-01-13 12:12:34 +01:00
commit 597f8a7bab
11 changed files with 242 additions and 11 deletions

View File

@ -1,6 +1,8 @@
1.21.3 -- 2014-mm-dd 1.22.0 -- 2014-mm-dd
-------------------- --------------------
New features:
- Health check endpoint
1.21.2 -- 2014-12-15 1.21.2 -- 2014-12-15
-------------------- --------------------
@ -19,6 +21,7 @@ Bugfixes:
- Closes fd for log files on `kill -HUP` (#230) - Closes fd for log files on `kill -HUP` (#230)
1.21.0 -- 2014-10-24 1.21.0 -- 2014-10-24
-------------------- --------------------

View File

@ -151,6 +151,14 @@ var config = {
// X-Tiler-Profile header containing elapsed timing for various // X-Tiler-Profile header containing elapsed timing for various
// steps taken for producing the response. // steps taken for producing the response.
,useProfiler:true ,useProfiler:true
// Settings for the health check available at /health
,health: {
enabled: false,
username: 'localhost',
z: 0,
x: 0,
y: 0
}
}; };
module.exports = config; module.exports = config;

View File

@ -160,6 +160,14 @@ var config = {
handler: 'inline' handler: 'inline'
} }
} }
// Settings for the health check available at /health
,health: {
enabled: true,
username: 'localhost',
z: 0,
x: 0,
y: 0
}
}; };
module.exports = config; module.exports = config;

View File

@ -160,6 +160,14 @@ var config = {
handler: 'inline' handler: 'inline'
} }
} }
// Settings for the health check available at /health
,health: {
enabled: false,
username: 'localhost',
z: 0,
x: 0,
y: 0
}
}; };
module.exports = config; module.exports = config;

View File

@ -147,6 +147,14 @@ var config = {
// X-Tiler-Profile header containing elapsed timing for various // X-Tiler-Profile header containing elapsed timing for various
// steps taken for producing the response. // steps taken for producing the response.
,useProfiler:true ,useProfiler:true
// Settings for the health check available at /health
,health: {
enabled: false,
username: 'localhost',
z: 0,
x: 0,
y: 0
}
}; };
module.exports = config; module.exports = config;

View File

@ -5,6 +5,7 @@ var _ = require('underscore')
, TemplateMaps = require('./template_maps.js') , TemplateMaps = require('./template_maps.js')
, Cache = require('./cache_validator') , Cache = require('./cache_validator')
, os = require('os') , os = require('os')
, HealthCheck = require('./monitoring/health_check')
; ;
if ( ! process.env['PGAPPNAME'] ) if ( ! process.env['PGAPPNAME'] )
@ -665,6 +666,31 @@ var CartodbWindshaft = function(serverOptions) {
// ---- Template maps interface ends @} // ---- Template maps interface ends @}
var healthCheck = new HealthCheck(cartoData, Windshaft.tilelive);
ws.get('/health', function(req, res) {
var healthConfig = global.environment.health || {};
if (!!healthConfig.enabled) {
var startTime = Date.now();
healthCheck.check(healthConfig, function(err, result) {
var ok = !err;
var response = {
enabled: true,
ok: ok,
elapsed: Date.now() - startTime,
result: result
};
if (err) {
response.err = err.message;
}
res.send(response, ok ? 200 : 503);
});
} else {
res.send({enabled: false, ok: true}, 200);
}
});
return ws; return ws;
}; };

View File

@ -0,0 +1,90 @@
var _ = require('underscore'),
dot = require('dot'),
fs = require('fs'),
path = require('path'),
Step = require('step');
function HealthCheck(metadataBackend, tilelive) {
this.metadataBackend = metadataBackend;
this.tilelive = tilelive;
}
module.exports = HealthCheck;
var mapnikOptions = {
query: {
metatile: 1,
poolSize: 4,
bufferSize: 64
},
protocol: 'mapnik:',
slashes: true,
xml: null
};
var xmlTemplate = dot.template(fs.readFileSync(path.resolve(__dirname, 'map-config.xml'), 'utf-8'));
HealthCheck.prototype.check = function(config, callback) {
var self = this,
startTime,
result = {
redis: {
ok: false
},
mapnik: {
ok: false
},
tile: {
ok: false
}
};
mapnikXmlParams = config;
Step(
function getDBParams() {
startTime = Date.now();
self.metadataBackend.getAllUserDBParams(config.username, this);
},
function loadMapnik(err, dbParams) {
if (err) {
throw err;
}
result.redis = {
ok: !err,
elapsed: Date.now() - startTime,
size: Object.keys(dbParams).length
};
mapnikOptions.xml = xmlTemplate(mapnikXmlParams);
startTime = Date.now();
self.tilelive.load(mapnikOptions, this);
},
function getTile(err, source) {
if (err) {
throw err;
}
result.mapnik = {
ok: !err,
elapsed: Date.now() - startTime
};
startTime = Date.now();
source.getTile(config.z, config.x, config.y, this);
},
function handleTile(err, tile) {
result.tile = {
ok: !err
};
if (tile) {
result.tile.elapsed = Date.now() - startTime;
result.tile.size = tile.length;
}
callback(err, result);
}
);
};

View File

@ -0,0 +1,4 @@
<Map
background-color="#c33"
srs="+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs">
</Map>

23
npm-shrinkwrap.json generated
View File

@ -96,7 +96,7 @@
}, },
"inherits": { "inherits": {
"version": "2.0.1", "version": "2.0.1",
"from": "inherits@2" "from": "inherits@~2.0.1"
} }
} }
} }
@ -149,6 +149,7 @@
"rollbar": { "rollbar": {
"version": "0.3.13", "version": "0.3.13",
"from": "rollbar@~0.3.13", "from": "rollbar@~0.3.13",
"resolved": "https://registry.npmjs.org/rollbar/-/rollbar-0.3.13.tgz",
"dependencies": { "dependencies": {
"node-uuid": { "node-uuid": {
"version": "1.4.2", "version": "1.4.2",
@ -302,21 +303,23 @@
"from": "tunnel-agent@~0.3.0" "from": "tunnel-agent@~0.3.0"
}, },
"http-signature": { "http-signature": {
"version": "0.10.0", "version": "0.10.1",
"from": "http-signature@~0.10.0", "from": "http-signature@~0.10.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.10.1.tgz",
"dependencies": { "dependencies": {
"assert-plus": { "assert-plus": {
"version": "0.1.2", "version": "0.1.5",
"from": "assert-plus@0.1.2" "from": "assert-plus@^0.1.5",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz"
}, },
"asn1": { "asn1": {
"version": "0.1.11", "version": "0.1.11",
"from": "asn1@0.1.11" "from": "asn1@0.1.11"
}, },
"ctype": { "ctype": {
"version": "0.5.2", "version": "0.5.3",
"from": "ctype@0.5.2", "from": "ctype@0.5.3",
"resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.2.tgz" "resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz"
} }
} }
}, },
@ -1615,9 +1618,9 @@
"resolved": "https://registry.npmjs.org/connect/-/connect-1.9.2.tgz", "resolved": "https://registry.npmjs.org/connect/-/connect-1.9.2.tgz",
"dependencies": { "dependencies": {
"formidable": { "formidable": {
"version": "1.0.15", "version": "1.0.16",
"from": "formidable@1.0.x", "from": "formidable@1.0.x",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-1.0.15.tgz" "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.0.16.tgz"
} }
} }
}, },
@ -1681,7 +1684,7 @@
}, },
"sphericalmercator": { "sphericalmercator": {
"version": "1.0.3", "version": "1.0.3",
"from": "sphericalmercator@~1.0.1", "from": "sphericalmercator@~1.0.2",
"resolved": "https://registry.npmjs.org/sphericalmercator/-/sphericalmercator-1.0.3.tgz" "resolved": "https://registry.npmjs.org/sphericalmercator/-/sphericalmercator-1.0.3.tgz"
} }
} }

View File

@ -1,6 +1,7 @@
{ {
"private": true, "private": true,
"name": "windshaft-cartodb", "name": "windshaft-cartodb",
"version": "1.22.0",
"version": "1.21.3", "version": "1.21.3",
"description": "A map tile server for CartoDB", "description": "A map tile server for CartoDB",
"keywords": [ "keywords": [

View File

@ -0,0 +1,72 @@
var helper = require(__dirname + '/../support/test_helper');
var assert = require('../support/assert');
var CartodbWindshaft = require(__dirname + '/../../lib/cartodb/cartodb_windshaft');
var serverOptions = require(__dirname + '/../../lib/cartodb/server_options')();
var server = new CartodbWindshaft(serverOptions);
suite('health checks', function () {
beforeEach(function (done) {
global.environment.health = {
enabled: true,
username: 'localhost',
z: 0,
x: 0,
y: 0
};
done();
});
var healthCheckRequest = {
url: '/health',
method: 'GET',
headers: {
host: 'localhost'
}
};
test('returns 200 and ok=true with enabled configuration', function (done) {
assert.response(server,
healthCheckRequest,
{
status: 200
},
function (res, err) {
console.log(res.body);
assert.ok(!err);
var parsed = JSON.parse(res.body);
assert.ok(parsed.enabled);
assert.ok(parsed.ok);
done();
}
);
});
test('fails for invalid user because it is not in redis', function (done) {
global.environment.health.username = 'invalid';
assert.response(server,
healthCheckRequest,
{
status: 503
},
function (res, err) {
assert.ok(!err);
var parsed = JSON.parse(res.body);
assert.equal(parsed.enabled, true);
assert.equal(parsed.ok, false);
assert.equal(parsed.result.redis.ok, false);
done();
}
);
});
});