WIP: implement timeout limit for raster
This commit is contained in:
parent
669707b26c
commit
87eb5407a8
@ -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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user