CartoDB-SQL-API/batch/job_backend.js

254 lines
6.7 KiB
JavaScript
Raw Normal View History

'use strict';
var queue = require('queue-async');
2016-01-21 22:33:42 +08:00
var JOBS_TTL_IN_SECONDS = global.settings.jobs_ttl_in_seconds || 48 * 3600; // 48 hours
var jobStatus = require('./job_status');
2016-05-14 00:50:55 +08:00
var finalStatus = [
jobStatus.CANCELLED,
jobStatus.DONE,
jobStatus.FAILED,
jobStatus.UNKNOWN
];
function JobBackend(metadataBackend, jobQueueProducer, jobPublisher, userIndexer) {
this.db = 5;
this.redisPrefix = 'batch:jobs:';
this.metadataBackend = metadataBackend;
this.jobQueueProducer = jobQueueProducer;
this.jobPublisher = jobPublisher;
this.userIndexer = userIndexer;
}
2016-05-16 07:22:47 +08:00
JobBackend.prototype.toRedisParams = function (data) {
var redisParams = [this.redisPrefix + data.job_id];
var obj = JSON.parse(JSON.stringify(data));
delete obj.job_id;
2016-05-14 00:50:55 +08:00
for (var property in obj) {
if (obj.hasOwnProperty(property)) {
redisParams.push(property);
2016-05-17 07:00:27 +08:00
// TODO: this should be moved to job model
2016-05-16 07:22:47 +08:00
if (property === 'query' && typeof obj[property] !== 'string') {
redisParams.push(JSON.stringify(obj[property]));
2016-05-17 07:00:27 +08:00
} else if (property === 'status' && typeof obj[property] !== 'string') {
redisParams.push(JSON.stringify(obj[property]));
2016-05-16 07:22:47 +08:00
} else {
redisParams.push(obj[property]);
}
}
2016-05-14 00:50:55 +08:00
}
2016-05-14 00:50:55 +08:00
return redisParams;
};
2016-05-16 07:22:47 +08:00
JobBackend.prototype.toObject = function (job_id, redisParams, redisValues) {
2016-05-14 00:50:55 +08:00
var obj = {};
2016-05-16 07:22:47 +08:00
redisParams.shift(); // job_id value
redisParams.pop(); // WARN: weird function pushed by metadataBackend
2016-05-14 00:50:55 +08:00
for (var i = 0; i < redisParams.length; i++) {
2016-05-17 07:00:27 +08:00
// TODO: this should be moved to job model
if (redisParams[i] === 'query' || redisParams[i] === 'status') {
2016-05-16 07:22:47 +08:00
try {
obj[redisParams[i]] = JSON.parse(redisValues[i]);
} catch (e) {
obj[redisParams[i]] = redisValues[i];
}
2016-05-16 17:56:44 +08:00
} else if (redisValues[i]) {
2016-05-16 07:22:47 +08:00
obj[redisParams[i]] = redisValues[i];
}
2016-05-14 00:50:55 +08:00
}
2016-05-16 07:22:47 +08:00
obj.job_id = job_id; // adds redisKey as object property
2016-05-14 00:50:55 +08:00
return obj;
};
2016-05-14 00:50:55 +08:00
// TODO: is it really necessary??
function isJobFound(redisValues) {
return redisValues[0] && redisValues[1] && redisValues[2] && redisValues[3] && redisValues[4];
}
JobBackend.prototype.get = function (job_id, callback) {
var self = this;
var redisParams = [
this.redisPrefix + job_id,
'user',
'status',
'query',
'created_at',
'updated_at',
2016-05-16 07:22:47 +08:00
'host',
'failed_reason'
];
2016-05-14 00:50:55 +08:00
this.metadataBackend.redisCmd(this.db, 'HMGET', redisParams , function (err, redisValues) {
if (err) {
return callback(err);
}
2016-05-14 00:50:55 +08:00
if (!isJobFound(redisValues)) {
var notFoundError = new Error('Job with id ' + job_id + ' not found');
notFoundError.name = 'NotFoundError';
return callback(notFoundError);
}
2016-05-16 07:22:47 +08:00
var jobData = self.toObject(job_id, redisParams, redisValues);
2016-05-14 00:50:55 +08:00
callback(null, jobData);
});
};
2016-05-14 00:50:55 +08:00
JobBackend.prototype.create = function (data, callback) {
var self = this;
2016-05-14 00:50:55 +08:00
self.get(data.job_id, function (err) {
if (err && err.name !== 'NotFoundError') {
return callback(err);
}
2016-05-14 00:50:55 +08:00
self.save(data, function (err, job) {
if (err) {
return callback(err);
}
self.jobQueueProducer.enqueue(data.job_id, data.host, function (err) {
if (err) {
return callback(err);
}
// broadcast to consumers
self.jobPublisher.publish(data.host);
self.userIndexer.add(data.user, data.job_id, function (err) {
if (err) {
return callback(err);
}
callback(null, job);
});
});
});
});
};
2016-05-14 00:50:55 +08:00
JobBackend.prototype.update = function (data, callback) {
var self = this;
2016-05-14 00:50:55 +08:00
self.get(data.job_id, function (err) {
if (err) {
return callback(err);
}
2016-05-14 00:50:55 +08:00
self.save(data, callback);
});
};
2016-05-14 00:50:55 +08:00
JobBackend.prototype.save = function (data, callback) {
var self = this;
2016-05-14 00:50:55 +08:00
var redisParams = self.toRedisParams(data);
2016-05-14 00:50:55 +08:00
self.metadataBackend.redisCmd(self.db, 'HMSET', redisParams , function (err) {
if (err) {
return callback(err);
}
2016-05-14 00:50:55 +08:00
self.setTTL(data, function (err) {
if (err) {
return callback(err);
}
2016-05-14 00:50:55 +08:00
self.get(data.job_id, function (err, job) {
if (err) {
return callback(err);
}
callback(null, job);
});
});
});
};
2016-05-16 17:56:44 +08:00
function isFinalStatus(status) {
2016-05-14 00:50:55 +08:00
return finalStatus.indexOf(status) !== -1;
}
2016-05-14 00:50:55 +08:00
JobBackend.prototype.setTTL = function (data, callback) {
var self = this;
var redisKey = this.redisPrefix + data.job_id;
2016-05-16 17:56:44 +08:00
if (!isFinalStatus(data.status)) {
2016-05-14 00:50:55 +08:00
return callback();
}
2016-05-14 00:50:55 +08:00
self.metadataBackend.redisCmd(self.db, 'EXPIRE', [ redisKey, JOBS_TTL_IN_SECONDS ], callback);
};
2016-05-14 00:50:55 +08:00
JobBackend.prototype.list = function (user, callback) {
var self = this;
2016-05-14 00:50:55 +08:00
this.userIndexer.list(user, function (err, job_ids) {
if (err) {
return callback(err);
}
2016-05-14 00:50:55 +08:00
var initialLength = job_ids.length;
self._getCleanedList(user, job_ids, function (err, jobs) {
if (err) {
return callback(err);
}
2016-05-14 00:50:55 +08:00
if (jobs.length < initialLength) {
return self.list(user, callback);
}
callback(null, jobs);
});
});
};
2016-05-14 00:50:55 +08:00
JobBackend.prototype._getCleanedList = function (user, job_ids, callback) {
var self = this;
2016-05-14 00:50:55 +08:00
var jobsQueue = queue(job_ids.length);
job_ids.forEach(function(job_id) {
jobsQueue.defer(self._getIndexedJob.bind(self), job_id, user);
});
2016-05-14 00:50:55 +08:00
jobsQueue.awaitAll(function (err, jobs) {
if (err) {
return callback(err);
}
2016-05-14 00:50:55 +08:00
callback(null, jobs.filter(function (job) {
return job ? true : false;
}));
});
};
2016-05-14 00:50:55 +08:00
JobBackend.prototype._getIndexedJob = function (job_id, user, callback) {
var self = this;
this.get(job_id, function (err, job) {
2016-05-14 00:50:55 +08:00
if (err && err.name === 'NotFoundError') {
return self.userIndexer.remove(user, job_id, function (err) {
if (err) {
console.error('Error removing key %s in user set', job_id, err);
}
callback();
});
}
if (err) {
return callback(err);
}
2016-05-14 00:50:55 +08:00
callback(null, job);
});
};
module.exports = JobBackend;