2018-10-24 21:42:33 +08:00
|
|
|
'use strict';
|
|
|
|
|
2018-02-17 01:21:06 +08:00
|
|
|
const util = require('util');
|
2015-12-07 16:40:51 +08:00
|
|
|
|
2019-10-04 01:35:18 +08:00
|
|
|
const bodyParser = require('../middlewares/body-parser');
|
|
|
|
const user = require('../middlewares/user');
|
|
|
|
const { initializeProfiler, finishProfiler } = require('../middlewares/profiler');
|
|
|
|
const authorization = require('../middlewares/authorization');
|
|
|
|
const connectionParams = require('../middlewares/connection-params');
|
|
|
|
const error = require('../middlewares/error');
|
|
|
|
const rateLimits = require('../middlewares/rate-limit');
|
|
|
|
const { RATE_LIMIT_ENDPOINTS_GROUPS } = rateLimits;
|
2019-07-29 22:24:48 +08:00
|
|
|
const params = require('../middlewares/params');
|
2019-10-04 01:35:18 +08:00
|
|
|
const log = require('../middlewares/log');
|
2015-12-07 16:40:51 +08:00
|
|
|
|
2019-10-01 16:59:44 +08:00
|
|
|
module.exports = class JobController {
|
|
|
|
constructor (metadataBackend, userDatabaseService, jobService, statsdClient, userLimitsService) {
|
|
|
|
this.metadataBackend = metadataBackend;
|
|
|
|
this.userDatabaseService = userDatabaseService;
|
|
|
|
this.jobService = jobService;
|
|
|
|
this.statsdClient = statsdClient;
|
|
|
|
this.userLimitsService = userLimitsService;
|
|
|
|
}
|
2016-05-24 20:28:00 +08:00
|
|
|
|
2019-10-02 22:02:13 +08:00
|
|
|
route (sqlRouter) {
|
2019-10-01 16:59:44 +08:00
|
|
|
const jobMiddlewares = composeJobMiddlewares(
|
|
|
|
this.metadataBackend,
|
|
|
|
this.userDatabaseService,
|
|
|
|
this.jobService,
|
|
|
|
this.statsdClient,
|
|
|
|
this.userLimitsService
|
|
|
|
);
|
2016-05-14 00:50:55 +08:00
|
|
|
|
2019-10-02 22:02:13 +08:00
|
|
|
sqlRouter.post('/job', [
|
2019-10-04 01:35:18 +08:00
|
|
|
bodyParser(),
|
2019-10-01 16:59:44 +08:00
|
|
|
checkBodyPayloadSize(),
|
|
|
|
params({ strategy: 'job' }),
|
2019-10-04 01:35:18 +08:00
|
|
|
log(log.TYPES.JOB),
|
2019-10-01 16:59:44 +08:00
|
|
|
jobMiddlewares('create', createJob, RATE_LIMIT_ENDPOINTS_GROUPS.JOB_CREATE)
|
2019-10-02 22:02:13 +08:00
|
|
|
]);
|
2019-10-01 18:25:22 +08:00
|
|
|
|
2019-10-02 22:02:13 +08:00
|
|
|
sqlRouter.get('/job/:job_id', [
|
2019-10-04 01:35:18 +08:00
|
|
|
bodyParser(),
|
2019-10-01 16:59:44 +08:00
|
|
|
jobMiddlewares('retrieve', getJob, RATE_LIMIT_ENDPOINTS_GROUPS.JOB_GET)
|
2019-10-02 22:02:13 +08:00
|
|
|
]);
|
2019-10-01 18:25:22 +08:00
|
|
|
|
2019-10-02 22:02:13 +08:00
|
|
|
sqlRouter.delete('/job/:job_id', [
|
2019-10-04 01:35:18 +08:00
|
|
|
bodyParser(),
|
2019-10-01 16:59:44 +08:00
|
|
|
jobMiddlewares('cancel', cancelJob, RATE_LIMIT_ENDPOINTS_GROUPS.JOB_DELETE)
|
2019-10-02 22:02:13 +08:00
|
|
|
]);
|
2019-10-01 16:59:44 +08:00
|
|
|
}
|
2015-12-31 03:16:18 +08:00
|
|
|
};
|
|
|
|
|
2018-03-20 23:20:56 +08:00
|
|
|
function composeJobMiddlewares (metadataBackend, userDatabaseService, jobService, statsdClient, userLimitsService) {
|
2019-10-04 01:35:18 +08:00
|
|
|
return function jobMiddlewares (action, job, endpointGroup) {
|
2018-06-05 19:14:50 +08:00
|
|
|
const forceToBeMaster = true;
|
2018-02-20 23:25:16 +08:00
|
|
|
|
|
|
|
return [
|
2019-10-04 01:35:18 +08:00
|
|
|
initializeProfiler('job'),
|
|
|
|
user(metadataBackend),
|
|
|
|
rateLimits(userLimitsService, endpointGroup),
|
|
|
|
authorization(metadataBackend, forceToBeMaster),
|
|
|
|
connectionParams(userDatabaseService),
|
|
|
|
job(jobService),
|
2018-02-20 23:25:16 +08:00
|
|
|
setServedByDBHostHeader(),
|
2019-10-04 01:35:18 +08:00
|
|
|
finishProfiler(),
|
2018-02-20 23:25:16 +08:00
|
|
|
logJobResult(action),
|
|
|
|
incrementSuccessMetrics(statsdClient),
|
2018-02-23 20:03:56 +08:00
|
|
|
sendResponse(),
|
2018-02-20 23:25:16 +08:00
|
|
|
incrementErrorMetrics(statsdClient),
|
2019-10-04 01:35:18 +08:00
|
|
|
error()
|
2018-02-20 23:25:16 +08:00
|
|
|
];
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-02-17 01:21:06 +08:00
|
|
|
function cancelJob (jobService) {
|
|
|
|
return function cancelJobMiddleware (req, res, next) {
|
|
|
|
const { job_id } = req.params;
|
2015-12-07 16:40:51 +08:00
|
|
|
|
2018-02-17 01:21:06 +08:00
|
|
|
jobService.cancel(job_id, (err, job) => {
|
2018-02-19 21:42:52 +08:00
|
|
|
if (req.profiler) {
|
|
|
|
req.profiler.done('cancelJob');
|
|
|
|
}
|
|
|
|
|
2018-02-17 01:21:06 +08:00
|
|
|
if (err) {
|
|
|
|
return next(err);
|
|
|
|
}
|
2015-12-25 00:42:49 +08:00
|
|
|
|
2018-02-23 20:03:56 +08:00
|
|
|
res.body = job.serialize();
|
2018-02-17 01:21:06 +08:00
|
|
|
|
|
|
|
next();
|
|
|
|
});
|
2016-10-04 22:07:13 +08:00
|
|
|
};
|
2018-02-17 01:21:06 +08:00
|
|
|
}
|
2016-05-14 00:50:55 +08:00
|
|
|
|
2018-02-17 01:21:06 +08:00
|
|
|
function getJob (jobService) {
|
|
|
|
return function getJobMiddleware (req, res, next) {
|
2018-02-19 18:04:28 +08:00
|
|
|
const { job_id } = req.params;
|
|
|
|
|
|
|
|
jobService.get(job_id, (err, job) => {
|
2018-02-19 21:42:52 +08:00
|
|
|
if (req.profiler) {
|
|
|
|
req.profiler.done('getJob');
|
|
|
|
}
|
|
|
|
|
2018-02-17 01:21:06 +08:00
|
|
|
if (err) {
|
|
|
|
return next(err);
|
|
|
|
}
|
2015-12-07 16:40:51 +08:00
|
|
|
|
2018-02-23 20:03:56 +08:00
|
|
|
res.body = job.serialize();
|
2016-10-28 21:08:42 +08:00
|
|
|
|
2018-02-17 01:21:06 +08:00
|
|
|
next();
|
|
|
|
});
|
|
|
|
};
|
|
|
|
}
|
2016-10-28 21:08:42 +08:00
|
|
|
|
2018-02-17 01:21:06 +08:00
|
|
|
function createJob (jobService) {
|
|
|
|
return function createJobMiddleware (req, res, next) {
|
|
|
|
var data = {
|
|
|
|
user: res.locals.user,
|
2019-07-26 22:06:53 +08:00
|
|
|
query: res.locals.params.sql,
|
2018-02-17 01:21:06 +08:00
|
|
|
host: res.locals.userDbParams.host,
|
2018-03-14 02:08:51 +08:00
|
|
|
port: global.settings.db_batch_port || res.locals.userDbParams.port,
|
2018-02-17 01:21:06 +08:00
|
|
|
pass: res.locals.userDbParams.pass,
|
|
|
|
dbname: res.locals.userDbParams.dbname,
|
|
|
|
dbuser: res.locals.userDbParams.user
|
|
|
|
};
|
2016-10-28 21:08:42 +08:00
|
|
|
|
2018-02-17 01:21:06 +08:00
|
|
|
jobService.create(data, (err, job) => {
|
2018-02-19 21:42:52 +08:00
|
|
|
if (req.profiler) {
|
|
|
|
req.profiler.done('createJob');
|
|
|
|
}
|
|
|
|
|
2018-02-17 01:21:06 +08:00
|
|
|
if (err) {
|
|
|
|
return next(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
res.locals.job_id = job.job_id;
|
|
|
|
|
2018-02-23 20:03:56 +08:00
|
|
|
res.statusCode = 201;
|
|
|
|
res.body = job.serialize();
|
2018-02-17 01:21:06 +08:00
|
|
|
|
|
|
|
next();
|
|
|
|
});
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function checkBodyPayloadSize () {
|
|
|
|
return function checkBodyPayloadSizeMiddleware(req, res, next) {
|
|
|
|
const payload = JSON.stringify(req.body);
|
|
|
|
|
|
|
|
if (payload.length > MAX_LIMIT_QUERY_SIZE_IN_BYTES) {
|
|
|
|
return next(new Error(getMaxSizeErrorMessage(payload)), res);
|
2016-10-28 21:08:42 +08:00
|
|
|
}
|
|
|
|
|
2018-02-17 01:21:06 +08:00
|
|
|
next();
|
|
|
|
};
|
|
|
|
}
|
2016-10-28 21:08:42 +08:00
|
|
|
|
2018-02-17 01:21:06 +08:00
|
|
|
const ONE_KILOBYTE_IN_BYTES = 1024;
|
|
|
|
const MAX_LIMIT_QUERY_SIZE_IN_KB = 16;
|
|
|
|
const MAX_LIMIT_QUERY_SIZE_IN_BYTES = MAX_LIMIT_QUERY_SIZE_IN_KB * ONE_KILOBYTE_IN_BYTES;
|
2016-01-07 19:06:01 +08:00
|
|
|
|
2018-02-17 01:21:06 +08:00
|
|
|
function getMaxSizeErrorMessage(sql) {
|
|
|
|
return util.format([
|
|
|
|
'Your payload is too large: %s bytes. Max size allowed is %s bytes (%skb).',
|
|
|
|
'Are you trying to import data?.',
|
|
|
|
'Please, check out import api http://docs.cartodb.com/cartodb-platform/import-api/'
|
|
|
|
].join(' '),
|
|
|
|
sql.length,
|
|
|
|
MAX_LIMIT_QUERY_SIZE_IN_BYTES,
|
|
|
|
Math.round(MAX_LIMIT_QUERY_SIZE_IN_BYTES / ONE_KILOBYTE_IN_BYTES)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports.MAX_LIMIT_QUERY_SIZE_IN_BYTES = MAX_LIMIT_QUERY_SIZE_IN_BYTES;
|
|
|
|
module.exports.getMaxSizeErrorMessage = getMaxSizeErrorMessage;
|
|
|
|
|
|
|
|
function setServedByDBHostHeader () {
|
|
|
|
return function setServedByDBHostHeaderMiddleware (req, res, next) {
|
|
|
|
const { userDbParams } = res.locals;
|
|
|
|
|
|
|
|
if (userDbParams.host) {
|
|
|
|
res.header('X-Served-By-DB-Host', res.locals.userDbParams.host);
|
2016-10-04 22:07:13 +08:00
|
|
|
}
|
2016-05-27 01:44:59 +08:00
|
|
|
|
2018-02-17 01:21:06 +08:00
|
|
|
next();
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-02-20 20:14:28 +08:00
|
|
|
function logJobResult (action) {
|
|
|
|
return function logJobResultMiddleware (req, res, next) {
|
2016-10-12 07:40:14 +08:00
|
|
|
if (process.env.NODE_ENV !== 'test') {
|
|
|
|
console.info(JSON.stringify({
|
|
|
|
type: 'sql_api_batch_job',
|
2017-11-24 22:49:25 +08:00
|
|
|
username: res.locals.user,
|
2016-10-12 07:40:14 +08:00
|
|
|
action: action,
|
2018-02-17 01:21:06 +08:00
|
|
|
job_id: req.params.job_id || res.locals.job_id
|
2016-10-12 07:40:14 +08:00
|
|
|
}));
|
|
|
|
}
|
2016-10-04 22:07:13 +08:00
|
|
|
|
2018-02-17 01:21:06 +08:00
|
|
|
next();
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
const METRICS_PREFIX = 'sqlapi.job';
|
|
|
|
|
|
|
|
function incrementSuccessMetrics (statsdClient) {
|
|
|
|
return function incrementSuccessMetricsMiddleware (req, res, next) {
|
|
|
|
if (statsdClient !== undefined) {
|
|
|
|
statsdClient.increment(`${METRICS_PREFIX}.success`);
|
|
|
|
}
|
|
|
|
|
|
|
|
next();
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function incrementErrorMetrics (statsdClient) {
|
|
|
|
return function incrementErrorMetricsMiddleware (err, req, res, next) {
|
|
|
|
if (statsdClient !== undefined) {
|
|
|
|
statsdClient.increment(`${METRICS_PREFIX}.error`);
|
|
|
|
}
|
|
|
|
|
|
|
|
next(err);
|
2016-10-04 22:07:13 +08:00
|
|
|
};
|
|
|
|
}
|
2018-02-23 20:03:56 +08:00
|
|
|
|
|
|
|
function sendResponse () {
|
|
|
|
return function sendResponseMiddleware (req, res) {
|
|
|
|
res.status(res.statusCode || 200).send(res.body);
|
|
|
|
};
|
|
|
|
}
|