WIP: implement timeout limit for raster

This commit is contained in:
Daniel García Aubert 2017-07-18 20:50:31 +02:00
parent 669707b26c
commit 87eb5407a8
3 changed files with 120 additions and 72 deletions

View File

@ -1,48 +1,25 @@
require('../support/test_helper'); require('../support/test_helper');
var assert = require('../support/assert'); const assert = require('../support/assert');
var TestClient = require('../support/test-client'); const TestClient = require('../support/test-client');
var testHelper = require('../support/test_helper');
var redis = require('redis'); const timeoutErrorTilePath = `${process.cwd()}/assets/render-timeout-fallback.png`;
var keysToDelete;
function withUserTimeoutRenderLimit(redisClient, user, userTimeoutLimit, callback) { var pointSleepSql = `
redisClient.SELECT(5, function(err) { SELECT
if (err) { pg_sleep(0.5),
return callback(err); 'SRID=3857;POINT(0 0)'::geometry the_geom_webmercator,
} 1 cartodb_id
`;
var userTimeoutLimitsKey = 'limits:timeout:' + user; function createMapConfig (sql = pointSleepSql, cartocss = TestClient.CARTOCSS.POINTS) {
var redisParams = [
userTimeoutLimitsKey,
'render', userTimeoutLimit,
'render_public', userTimeoutLimit
];
redisClient.hmset(redisParams, function (err) {
if (err) {
return callback(err);
}
keysToDelete[userTimeoutLimitsKey] = 5;
return callback();
});
});
}
function createMapConfig (cartocss) {
return { return {
version: '1.6.0', version: '1.6.0',
layers: [{ layers: [{
type: "cartodb", type: 'cartodb',
options: { options: {
sql: [ sql,
'SELECT', cartocss,
' pg_sleep(1),',
' 1 cartodb_id,',
' \'SRID=3857;POINT(0 0)\'::geometry the_geom_webmercator'
].join('\n'),
cartocss: cartocss,
cartocss_version: '2.3.0', cartocss_version: '2.3.0',
interactivity: 'cartodb_id' interactivity: 'cartodb_id'
} }
@ -51,28 +28,63 @@ function createMapConfig (cartocss) {
} }
describe('user timeout limits', function () { describe('user timeout limits', function () {
var redisClient = redis.createClient(global.environment.redis.port); describe('with onTileErrorStrategy ENABLED', function () {
let onTileErrorStrategy;
beforeEach(function() { before(function () {
keysToDelete = {}; onTileErrorStrategy = global.environment.enabledFeatures.onTileErrorStrategy;
global.environment.enabledFeatures.onTileErrorStrategy = true;
});
after(function () {
global.environment.enabledFeatures.onTileErrorStrategy = onTileErrorStrategy;
});
it('layergroup creation works if test tile is fast but tile request fails if they are slow', function (done) {
var testClient = new TestClient(createMapConfig(), 1234);
testClient.setUserRenderTimeoutLimit('localhost', 50, function (err) {
assert.ifError(err);
testClient.getTile(0, 0, 0, {}, function (err, res, tile) {
assert.ifError(err);
assert.imageIsSimilarToFile(tile, timeoutErrorTilePath, 0.05, function (err) {
assert.ifError(err);
testClient.drain(done);
});
});
});
});
}); });
afterEach(function (done) { describe('with onTileErrorStrategy DISABLED', function() {
testHelper.deleteRedisKeys(keysToDelete, done); var onTileErrorStrategy;
});
it('layergroup creation works even if test tile is slow', function (done) { beforeEach(function() {
withUserTimeoutRenderLimit(redisClient, 'localhost', 1, function (err) { onTileErrorStrategy = global.environment.enabledFeatures.onTileErrorStrategy;
if (err) { global.environment.enabledFeatures.onTileErrorStrategy = false;
return done(err); });
}
var mapConfig = createMapConfig(TestClient.CARTOCSS.POINTS); afterEach(function() {
var testClient = new TestClient(mapConfig, 1234); global.environment.enabledFeatures.onTileErrorStrategy = onTileErrorStrategy;
testClient.getTile(4, 4, 4, {}, function (err /*, res, tile */) { });
assert.ok(err, err);
// TODO: check timeout tile it('layergroup creation works even if test tile is slow', function (done) {
testClient.drain(done); var testClient = new TestClient(createMapConfig(), 1234);
testClient.setUserRenderTimeoutLimit('localhost', 50, function (err) {
assert.ifError(err);
var params = {
status: 400,
contentType: 'application/json; charset=utf-8'
};
testClient.getTile(0, 0, 0, params, function (err, res, tile) {
assert.ifError(err);
assert.equal(tile.errors[0], 'Render timed out');
testClient.drain(done);
});
}); });
}); });
}); });

View File

@ -14,13 +14,13 @@ var helper = require('./test_helper');
var CartodbWindshaft = require('../../lib/cartodb/server'); var CartodbWindshaft = require('../../lib/cartodb/server');
var serverOptions = require('../../lib/cartodb/server_options'); var serverOptions = require('../../lib/cartodb/server_options');
serverOptions.analysis.batch.inlineExecution = true; serverOptions.analysis.batch.inlineExecution = true;
var server = new CartodbWindshaft(serverOptions);
function TestClient(config, apiKey) { function TestClient(config, apiKey) {
this.mapConfig = isMapConfig(config) ? config : null; this.mapConfig = isMapConfig(config) ? config : null;
this.template = isTemplate(config) ? config : null; this.template = isTemplate(config) ? config : null;
this.apiKey = apiKey; this.apiKey = apiKey;
this.keysToDelete = {}; this.keysToDelete = {};
this.server = new CartodbWindshaft(serverOptions);
} }
module.exports = TestClient; module.exports = TestClient;
@ -97,7 +97,7 @@ TestClient.prototype.getWidget = function(widgetName, params, callback) {
step( step(
function createLayergroup() { function createLayergroup() {
var next = this; var next = this;
assert.response(server, assert.response(self.server,
{ {
url: url, url: url,
method: 'POST', method: 'POST',
@ -156,7 +156,7 @@ TestClient.prototype.getWidget = function(widgetName, params, callback) {
url = '/api/v1/map/' + layergroupId + '/0/widget/' + widgetName + '?' + qs.stringify(urlParams); url = '/api/v1/map/' + layergroupId + '/0/widget/' + widgetName + '?' + qs.stringify(urlParams);
assert.response(server, assert.response(self.server,
{ {
url: url, url: url,
method: 'GET', method: 'GET',
@ -208,7 +208,7 @@ TestClient.prototype.widgetSearch = function(widgetName, userQuery, params, call
step( step(
function createLayergroup() { function createLayergroup() {
var next = this; var next = this;
assert.response(server, assert.response(self.server,
{ {
url: url, url: url,
method: 'POST', method: 'POST',
@ -265,7 +265,7 @@ TestClient.prototype.widgetSearch = function(widgetName, userQuery, params, call
} }
url = '/api/v1/map/' + layergroupId + '/0/widget/' + widgetName + '/search?' + qs.stringify(urlParams); url = '/api/v1/map/' + layergroupId + '/0/widget/' + widgetName + '/search?' + qs.stringify(urlParams);
assert.response(server, assert.response(self.server,
{ {
url: url, url: url,
method: 'GET', method: 'GET',
@ -332,7 +332,7 @@ TestClient.prototype.getDataview = function(dataviewName, params, callback) {
step( step(
function createLayergroup() { function createLayergroup() {
var next = this; var next = this;
assert.response(server, assert.response(self.server,
{ {
url: url, url: url,
method: 'POST', method: 'POST',
@ -385,7 +385,7 @@ TestClient.prototype.getDataview = function(dataviewName, params, callback) {
} }
url = '/api/v1/map/' + layergroupId + '/dataview/' + dataviewName + '?' + qs.stringify(urlParams); url = '/api/v1/map/' + layergroupId + '/dataview/' + dataviewName + '?' + qs.stringify(urlParams);
assert.response(server, assert.response(self.server,
{ {
url: url, url: url,
method: 'GET', method: 'GET',
@ -441,7 +441,7 @@ TestClient.prototype.getTile = function(z, x, y, params, callback) {
params.placeholders = params.placeholders || {}; params.placeholders = params.placeholders || {};
assert.response(server, assert.response(self.server,
{ {
url: urlNamed + '?' + qs.stringify({ api_key: self.apiKey }), url: urlNamed + '?' + qs.stringify({ api_key: self.apiKey }),
method: 'POST', method: 'POST',
@ -473,7 +473,7 @@ TestClient.prototype.getTile = function(z, x, y, params, callback) {
urlNamed + '/' + templateId + '?' + qs.stringify({api_key: self.apiKey}) : urlNamed + '/' + templateId + '?' + qs.stringify({api_key: self.apiKey}) :
url; url;
assert.response(server, assert.response(self.server,
{ {
url: path, url: path,
method: 'POST', method: 'POST',
@ -569,20 +569,22 @@ TestClient.prototype.getTile = function(z, x, y, params, callback) {
expectedResponse.headers['Content-Type'] = 'application/json; charset=utf-8'; expectedResponse.headers['Content-Type'] = 'application/json; charset=utf-8';
} }
assert.response(server, request, expectedResponse, function(res, err) { if (params.contentType) {
expectedResponse.headers['Content-Type'] = 'application/json; charset=utf-8';
}
assert.response(self.server, request, expectedResponse, function(res, err) {
assert.ifError(err); assert.ifError(err);
var obj; var obj;
if (isPng) { if (isPng) {
obj = mapnik.Image.fromBytes(new Buffer(res.body, 'binary')); obj = mapnik.Image.fromBytes(new Buffer(res.body, 'binary'));
} } else if (isMvt) {
else if (isMvt) {
if (res.body) { if (res.body) {
obj = new mapnik.VectorTile(z, x, y); obj = new mapnik.VectorTile(z, x, y);
obj.setDataSync(new Buffer(res.body, 'binary')); obj.setDataSync(new Buffer(res.body, 'binary'));
} }
} } else {
else {
obj = JSON.parse(res.body); obj = JSON.parse(res.body);
} }
@ -618,7 +620,7 @@ TestClient.prototype.getLayergroup = function(expectedResponse, callback) {
url += '?' + qs.stringify({api_key: this.apiKey}); url += '?' + qs.stringify({api_key: this.apiKey});
} }
assert.response(server, assert.response(self.server,
{ {
url: url, url: url,
method: 'POST', method: 'POST',
@ -662,7 +664,7 @@ TestClient.prototype.getNodeStatus = function(nodeName, callback) {
step( step(
function createLayergroup() { function createLayergroup() {
var next = this; var next = this;
assert.response(server, assert.response(self.server,
{ {
url: url, url: url,
method: 'POST', method: 'POST',
@ -723,7 +725,7 @@ TestClient.prototype.getNodeStatus = function(nodeName, callback) {
} }
}; };
assert.response(server, request, expectedResponse, function(res, err) { assert.response(self.server, request, expectedResponse, function(res, err) {
assert.ifError(err); assert.ifError(err);
next(null, res, JSON.parse(res.body)); next(null, res, JSON.parse(res.body));
}); });
@ -741,6 +743,10 @@ TestClient.prototype.drain = function(callback) {
}; };
module.exports.getStaticMap = function getStaticMap(templateName, params, callback) { module.exports.getStaticMap = function getStaticMap(templateName, params, callback) {
var self = this;
self.server = new CartodbWindshaft(serverOptions);
if (!callback) { if (!callback) {
callback = params; callback = params;
params = null; params = null;
@ -771,9 +777,22 @@ module.exports.getStaticMap = function getStaticMap(templateName, params, callba
// this could be removed once named maps are invalidated, otherwise you hits the cache // this could be removed once named maps are invalidated, otherwise you hits the cache
var server = new CartodbWindshaft(serverOptions); var server = new CartodbWindshaft(serverOptions);
assert.response(server, requestOptions, expectedResponse, function (res, err) { assert.response(self.server, requestOptions, expectedResponse, function (res, err) {
helper.deleteRedisKeys({'user:localhost:mapviews:global': 5}, function() { helper.deleteRedisKeys({'user:localhost:mapviews:global': 5}, function() {
return callback(err, mapnik.Image.fromBytes(new Buffer(res.body, 'binary'))); return callback(err, mapnik.Image.fromBytes(new Buffer(res.body, 'binary')));
}); });
}); });
}; };
TestClient.prototype.setUserRenderTimeoutLimit = function (user, userTimeoutLimit, callback) {
const userTimeoutLimitsKey = `limits:timeout:${user}`;
const params = [
userTimeoutLimitsKey,
'render', userTimeoutLimit,
'render_public', userTimeoutLimit
];
this.keysToDelete[userTimeoutLimitsKey] = 5;
helper.configureMetadata('hmset', params, callback);
}

View File

@ -166,12 +166,29 @@ function rmdirRecursiveSync(dirname) {
} }
} }
function configureMetadata(action, params, callback) {
redisClient.SELECT(5, function (err) {
if (err) {
return callback(err);
}
redisClient[action](params, function (err) {
if (err) {
return callback(err);
}
return callback();
});
});
}
module.exports = { module.exports = {
deleteRedisKeys: deleteRedisKeys, deleteRedisKeys: deleteRedisKeys,
lzma_compress_to_base64: lzma_compress_to_base64, lzma_compress_to_base64: lzma_compress_to_base64,
checkNoCache: checkNoCache, checkNoCache: checkNoCache,
checkSurrogateKey: checkSurrogateKey, checkSurrogateKey: checkSurrogateKey,
checkCache: checkCache, checkCache: checkCache,
rmdirRecursiveSync: rmdirRecursiveSync rmdirRecursiveSync: rmdirRecursiveSync,
configureMetadata
}; };