Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab4a0e836f | ||
|
|
abe02db6c6 | ||
|
|
a2cd5dd32d | ||
|
|
49b46a6096 | ||
|
|
5e530105df | ||
|
|
2f82d34c4b | ||
|
|
81fd01d0ac | ||
|
|
9faac9f9fe | ||
|
|
d04787a60c | ||
|
|
f5dbf94b52 | ||
|
|
5bec2d9b15 | ||
|
|
fe64f0c63c | ||
|
|
c20fd9691a | ||
|
|
eb323fbff9 | ||
|
|
211f6b9a74 | ||
|
|
b6c003ec63 | ||
|
|
93d4bf2a72 | ||
|
|
c6cb573383 | ||
|
|
f4ce671ea4 | ||
|
|
147f7cbabb | ||
|
|
b05d5a141e | ||
|
|
d34e0306f8 | ||
|
|
bd9f48dd24 | ||
|
|
9805990d79 | ||
|
|
dbbe60967c | ||
|
|
0ef91c1904 | ||
|
|
376573459c | ||
|
|
9c6d7c0ff9 | ||
|
|
30a95b7da3 |
43
NEWS.md
43
NEWS.md
@@ -1,5 +1,48 @@
|
||||
# Changelog
|
||||
|
||||
## 2.81.1
|
||||
Released 2016-11-05
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to [2.6.2](https://github.com/CartoDB/windshaft/releases/tag/2.6.2).
|
||||
- Upgrades camshaft to [0.46.3](https://github.com/CartoDB/camshaft/releases/tag/0.46.3).
|
||||
|
||||
|
||||
## 2.81.0
|
||||
Released 2016-11-02
|
||||
|
||||
Enhancements:
|
||||
- Returns errors with context when query layer does not retrieve geometry column
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to [2.6.1](https://github.com/CartoDB/windshaft/releases/tag/2.6.1).
|
||||
- Upgrades camshaft to [0.46.2](https://github.com/CartoDB/camshaft/releases/tag/0.46.2).
|
||||
|
||||
|
||||
## 2.80.2
|
||||
Released 2016-10-26
|
||||
|
||||
Bug fixes:
|
||||
- Fix order in categories query to get ramps
|
||||
|
||||
|
||||
## 2.80.1
|
||||
Released 2016-10-25
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.46.1](https://github.com/CartoDB/camshaft/releases/tag/0.46.1).
|
||||
|
||||
|
||||
## 2.80.0
|
||||
Released 2016-10-20
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.46.0](https://github.com/CartoDB/camshaft/releases/tag/0.46.0).
|
||||
|
||||
New features:
|
||||
- Default analyses limits can be defined in configuration.
|
||||
|
||||
|
||||
## 2.79.0
|
||||
Released 2016-10-11
|
||||
|
||||
|
||||
@@ -216,6 +216,12 @@ var config = {
|
||||
// there, in append mode. Otherwise 'log_filename' is used. Otherwise stdout is used (default).
|
||||
// Log file will be re-opened on receiving the HUP signal
|
||||
filename: '/tmp/analysis.log'
|
||||
},
|
||||
// Define max execution time in ms for analyses or tags
|
||||
// If analysis or tag are not found in redis this values will be used as default.
|
||||
limits: {
|
||||
moran: 120000,
|
||||
cpu2x: 60000
|
||||
}
|
||||
}
|
||||
,millstone: {
|
||||
|
||||
@@ -210,6 +210,12 @@ var config = {
|
||||
// there, in append mode. Otherwise 'log_filename' is used. Otherwise stdout is used (default).
|
||||
// Log file will be re-opened on receiving the HUP signal
|
||||
filename: 'logs/analysis.log'
|
||||
},
|
||||
// Define max execution time in ms for analyses or tags
|
||||
// If analysis or tag are not found in redis this values will be used as default.
|
||||
limits: {
|
||||
moran: 120000,
|
||||
cpu2x: 60000
|
||||
}
|
||||
}
|
||||
,millstone: {
|
||||
|
||||
@@ -210,6 +210,12 @@ var config = {
|
||||
// there, in append mode. Otherwise 'log_filename' is used. Otherwise stdout is used (default).
|
||||
// Log file will be re-opened on receiving the HUP signal
|
||||
filename: 'logs/analysis.log'
|
||||
},
|
||||
// Define max execution time in ms for analyses or tags
|
||||
// If analysis or tag are not found in redis this values will be used as default.
|
||||
limits: {
|
||||
moran: 120000,
|
||||
cpu2x: 60000
|
||||
}
|
||||
}
|
||||
,millstone: {
|
||||
|
||||
@@ -211,6 +211,12 @@ var config = {
|
||||
// there, in append mode. Otherwise 'log_filename' is used. Otherwise stdout is used (default).
|
||||
// Log file will be re-opened on receiving the HUP signal
|
||||
filename: 'node-windshaft.log'
|
||||
},
|
||||
// Define max execution time in ms for analyses or tags
|
||||
// If analysis or tag are not found in redis this values will be used as default.
|
||||
limits: {
|
||||
moran: 120000,
|
||||
cpu2x: 60000
|
||||
}
|
||||
}
|
||||
,millstone: {
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
var _ = require('underscore');
|
||||
var camshaft = require('camshaft');
|
||||
var fs = require('fs');
|
||||
|
||||
@@ -8,9 +11,9 @@ var REDIS_LIMITS = {
|
||||
|
||||
function AnalysisBackend (metadataBackend, options) {
|
||||
this.metadataBackend = metadataBackend;
|
||||
options = options || {};
|
||||
this.setBatchConfig(options.batch);
|
||||
this.setLoggerConfig(options.logger);
|
||||
this.options = options || {};
|
||||
this.setBatchConfig(this.options.batch);
|
||||
this.setLoggerConfig(this.options.logger);
|
||||
}
|
||||
|
||||
module.exports = AnalysisBackend;
|
||||
@@ -55,10 +58,13 @@ AnalysisBackend.prototype.create = function(analysisConfiguration, analysisDefin
|
||||
};
|
||||
|
||||
AnalysisBackend.prototype.getAnalysesLimits = function(username, callback) {
|
||||
var self = this;
|
||||
var analysesLimitsKey = REDIS_LIMITS.PREFIX + username;
|
||||
this.metadataBackend.redisCmd(REDIS_LIMITS.DB, 'HGETALL', [analysesLimitsKey], function(err, analysesTimeouts) {
|
||||
analysesTimeouts = analysesTimeouts || {};
|
||||
|
||||
_.defaults(analysesTimeouts, self.options.limits);
|
||||
|
||||
var analysesLimits = {
|
||||
analyses: {
|
||||
// buffer: {
|
||||
|
||||
@@ -32,7 +32,7 @@ methodTemplates.category = dot.template([
|
||||
' SELECT {{=it._column}} AS category, count(1) AS value, row_number() OVER (ORDER BY count(1) desc) as rank',
|
||||
' FROM ({{=it._sql}}) _cdb_aggregation_all',
|
||||
' GROUP BY {{=it._column}}',
|
||||
' ORDER BY 2 DESC',
|
||||
' ORDER BY 2 DESC, 1 ASC',
|
||||
'),',
|
||||
'agg_categories AS (',
|
||||
' SELECT category',
|
||||
|
||||
@@ -166,6 +166,24 @@ MapController.prototype.create = function(req, res, prepareConfigFn) {
|
||||
},
|
||||
function finish(err, layergroup) {
|
||||
if (err) {
|
||||
if (Number.isFinite(err.layerIndex)) {
|
||||
var error = new Error(err.message);
|
||||
error.http_status = err.http_status;
|
||||
|
||||
if (!err.http_status && err.message.indexOf('column "the_geom_webmercator" does not exist') >= 0) {
|
||||
error.http_status = 400;
|
||||
}
|
||||
|
||||
error.type = 'layer';
|
||||
error.subtype = err.message.indexOf('Postgis Plugin') >= 0 ? 'query' : undefined;
|
||||
error.layer = {
|
||||
id: mapConfig.getLayerId(err.layerIndex),
|
||||
index: err.layerIndex,
|
||||
type: mapConfig.layerType(err.layerIndex)
|
||||
};
|
||||
|
||||
err = error;
|
||||
}
|
||||
self.sendError(req, res, err, 'ANONYMOUS LAYERGROUP');
|
||||
} else {
|
||||
var analysesResults = context.analysesResults || [];
|
||||
|
||||
@@ -39,7 +39,8 @@ var analysisConfig = _.defaults(global.environment.analysis || {}, {
|
||||
},
|
||||
logger: {
|
||||
filename: undefined
|
||||
}
|
||||
},
|
||||
limits: {}
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
@@ -101,7 +102,8 @@ module.exports = {
|
||||
},
|
||||
logger: {
|
||||
filename: analysisConfig.logger.filename
|
||||
}
|
||||
},
|
||||
limits: analysisConfig.limits
|
||||
},
|
||||
// Do not send unwatch on release. See http://github.com/CartoDB/Windshaft-cartodb/issues/161
|
||||
redis: _.extend(global.environment.redis, {unwatchOnRelease: false}),
|
||||
|
||||
900
npm-shrinkwrap.json
generated
900
npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "windshaft-cartodb",
|
||||
"version": "2.79.0",
|
||||
"version": "2.81.1",
|
||||
"description": "A map tile server for CartoDB",
|
||||
"keywords": [
|
||||
"cartodb"
|
||||
@@ -20,7 +20,7 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"body-parser": "~1.14.0",
|
||||
"camshaft": "0.45.0",
|
||||
"camshaft": "0.46.3",
|
||||
"cartodb-psql": "~0.6.1",
|
||||
"cartodb-query-tables": "~0.1.0",
|
||||
"cartodb-redis": "0.13.1",
|
||||
@@ -39,7 +39,7 @@
|
||||
"step-profiler": "~0.3.0",
|
||||
"turbo-carto": "0.18.0",
|
||||
"underscore": "~1.6.0",
|
||||
"windshaft": "2.5.0",
|
||||
"windshaft": "2.6.2",
|
||||
"yargs": "~5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
68
test/acceptance/errors-with-context.js
Normal file
68
test/acceptance/errors-with-context.js
Normal file
@@ -0,0 +1,68 @@
|
||||
var assert = require('../support/assert');
|
||||
var CartodbWindshaft = require(__dirname + '/../../lib/cartodb/server');
|
||||
var serverOptions = require(__dirname + '/../../lib/cartodb/server_options');
|
||||
var server = new CartodbWindshaft(serverOptions);
|
||||
|
||||
describe('error with context', function () {
|
||||
var layerOK = {
|
||||
options: {
|
||||
sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, 5e6, 0) as the_geom_webmercator ' +
|
||||
'from test_table',
|
||||
cartocss: '#layer { marker-fill:red; marker-width:32; marker-allow-overlap:true; }',
|
||||
cartocss_version: '2.0.1',
|
||||
interactivity: 'cartodb_id'
|
||||
}
|
||||
};
|
||||
var layerKO = {
|
||||
options: {
|
||||
sql: 'select cartodb_id from test_table offset 3', // it doesn't return the_geom_webmercator so it must fail
|
||||
cartocss: '#layer { marker-fill:blue; marker-allow-overlap:true; }',
|
||||
cartocss_version: '2.0.2',
|
||||
interactivity: 'cartodb_id'
|
||||
}
|
||||
};
|
||||
|
||||
var DB_ERROR_MESSAGE = 'Postgis Plugin: ERROR: column "the_geom_webmercator" does not exist';
|
||||
var scenarios = [{
|
||||
description: 'layergroup with 2 layers, second one has query error',
|
||||
layergroup: {
|
||||
version: '1.0.0',
|
||||
layers: [layerOK, layerKO]
|
||||
},
|
||||
expectedFailingLayer: { id: 'layer1', index: 1, type: 'mapnik' }
|
||||
}, {
|
||||
description: 'layergroup with 2 layers, first one has query error',
|
||||
layergroup: {
|
||||
version: '1.0.0',
|
||||
layers: [layerKO, layerOK]
|
||||
},
|
||||
expectedFailingLayer: { id: 'layer0', index: 0, type: 'mapnik' }
|
||||
}];
|
||||
|
||||
scenarios.forEach(function (scenario) {
|
||||
it(scenario.description, function (done) {
|
||||
assert.response(server, {
|
||||
url: '/api/v1/map',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
host: 'localhost',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify(scenario.layergroup)
|
||||
}, {
|
||||
status: 400
|
||||
}, function (res) {
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
|
||||
assert.ok(Array.isArray(parsedBody.errors_with_context));
|
||||
|
||||
var err = parsedBody.errors_with_context[0];
|
||||
assert.equal(err.type, 'layer');
|
||||
assert.equal(err.subtype, 'query');
|
||||
assert.ok(err.message.indexOf(DB_ERROR_MESSAGE) >= 0);
|
||||
assert.deepEqual(err.layer, scenario.expectedFailingLayer);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
127
test/integration/analysis-backend-limits.js
Normal file
127
test/integration/analysis-backend-limits.js
Normal file
@@ -0,0 +1,127 @@
|
||||
var testHelper = require('../support/test_helper');
|
||||
|
||||
var assert = require('assert');
|
||||
var redis = require('redis');
|
||||
|
||||
var RedisPool = require('redis-mpool');
|
||||
var cartodbRedis = require('cartodb-redis');
|
||||
|
||||
var AnalysisBackend = require('../../lib/cartodb/backends/analysis');
|
||||
|
||||
describe('analysis-backend limits', function() {
|
||||
|
||||
var redisClient;
|
||||
var keysToDelete;
|
||||
var user = 'localhost';
|
||||
|
||||
beforeEach(function() {
|
||||
redisClient = redis.createClient(global.environment.redis.port);
|
||||
keysToDelete = {};
|
||||
var redisPool = new RedisPool(global.environment.redis);
|
||||
this.metadataBackend = cartodbRedis({pool: redisPool});
|
||||
});
|
||||
|
||||
afterEach(function(done) {
|
||||
redisClient.quit(function() {
|
||||
testHelper.deleteRedisKeys(keysToDelete, done);
|
||||
});
|
||||
});
|
||||
|
||||
function withAnalysesLimits(limits, callback) {
|
||||
redisClient.SELECT(5, function(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
var analysesLimitsKey = 'limits:analyses:' + user;
|
||||
redisClient.HMSET([analysesLimitsKey].concat(limits), function(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
keysToDelete[analysesLimitsKey] = 5;
|
||||
return callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
it("should use limits from configuration", function(done) {
|
||||
var analysisBackend = new AnalysisBackend(this.metadataBackend, { limits: { moran: 5000, kmeans: 5000 } });
|
||||
analysisBackend.getAnalysesLimits(user, function(err, result) {
|
||||
assert.ok(!err, err);
|
||||
|
||||
assert.ok(result.analyses.moran);
|
||||
assert.equal(result.analyses.moran.timeout, 5000);
|
||||
|
||||
assert.ok(result.analyses.kmeans);
|
||||
assert.equal(result.analyses.kmeans.timeout, 5000);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should use limits from redis", function(done) {
|
||||
var self = this;
|
||||
var limits = ['moran', 5000];
|
||||
|
||||
withAnalysesLimits(limits, function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
var analysisBackend = new AnalysisBackend(self.metadataBackend);
|
||||
analysisBackend.getAnalysesLimits(user, function(err, result) {
|
||||
assert.ok(!err, err);
|
||||
|
||||
assert.ok(result.analyses.moran);
|
||||
assert.equal(result.analyses.moran.timeout, 5000);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should use limits from redis and configuration, redis takes priority", function(done) {
|
||||
var self = this;
|
||||
var limits = ['moran', 5000];
|
||||
|
||||
withAnalysesLimits(limits, function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
var analysisBackend = new AnalysisBackend(self.metadataBackend, { limits: { moran: 1000 } });
|
||||
analysisBackend.getAnalysesLimits(user, function(err, result) {
|
||||
assert.ok(!err, err);
|
||||
|
||||
assert.ok(result.analyses.moran);
|
||||
assert.equal(result.analyses.moran.timeout, 5000);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should use limits from redis and configuration, defaulting for values not present in redis", function(done) {
|
||||
var self = this;
|
||||
var limits = ['moran', 5000];
|
||||
|
||||
withAnalysesLimits(limits, function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
var analysisBackend = new AnalysisBackend(self.metadataBackend, { limits: { moran: 1000, kmeans: 1000 } });
|
||||
analysisBackend.getAnalysesLimits(user, function(err, result) {
|
||||
assert.ok(!err, err);
|
||||
|
||||
assert.ok(result.analyses.moran);
|
||||
assert.equal(result.analyses.moran.timeout, 5000);
|
||||
|
||||
assert.ok(result.analyses.kmeans);
|
||||
assert.equal(result.analyses.kmeans.timeout, 1000);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -75,7 +75,7 @@ if test x"$PREPARE_PGSQL" = xyes; then
|
||||
dropdb "${TEST_DB}"
|
||||
createdb -Ttemplate_postgis -EUTF8 "${TEST_DB}" || die "Could not create test database"
|
||||
|
||||
LOCAL_SQL_SCRIPTS='analysis_catalog windshaft.test gadm4 ported/populated_places_simple_reduced'
|
||||
LOCAL_SQL_SCRIPTS='analysis_catalog windshaft.test gadm4 ported/populated_places_simple_reduced cdb_analysis_check'
|
||||
REMOTE_SQL_SCRIPTS='CDB_QueryStatements CDB_QueryTables CDB_CartodbfyTable CDB_TableMetadata CDB_ForeignTable CDB_UserTables CDB_ColumnNames CDB_ZoomFromScale CDB_OverviewsSupport CDB_Overviews CDB_QuantileBins CDB_JenksBins CDB_HeadsTailsBins CDB_EqualIntervalBins CDB_Hexagon CDB_XYZ'
|
||||
|
||||
CURL_ARGS=""
|
||||
|
||||
6
test/support/sql/cdb_analysis_check.sql
Normal file
6
test/support/sql/cdb_analysis_check.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
CREATE OR REPLACE FUNCTION CDB_CheckAnalysisQuota(table_name TEXT)
|
||||
RETURNS void AS
|
||||
$$
|
||||
BEGIN
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL;
|
||||
@@ -634,7 +634,8 @@ GRANT SELECT ON TABLE analysis_rent_listings TO :PUBLICUSER;
|
||||
--
|
||||
GRANT SELECT, UPDATE, INSERT, DELETE ON cdb_analysis_catalog TO :TESTUSER;
|
||||
|
||||
create schema cdb_crankshaft;
|
||||
DROP EXTENSION IF EXISTS crankshaft;
|
||||
CREATE SCHEMA IF NOT EXISTS cdb_crankshaft;
|
||||
GRANT USAGE ON SCHEMA cdb_crankshaft TO :TESTUSER;
|
||||
CREATE TYPE kmeans_type as (cartodb_id numeric, cluster_no numeric);
|
||||
CREATE OR REPLACE FUNCTION cdb_crankshaft.CDB_KMeans(query text, no_clusters integer,no_init integer default 20)
|
||||
|
||||
Reference in New Issue
Block a user