2015-12-22 02:57:10 +08:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
var uuid = require('node-uuid');
|
2015-12-25 00:42:49 +08:00
|
|
|
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
|
2015-12-22 02:57:10 +08:00
|
|
|
|
2015-12-25 00:42:49 +08:00
|
|
|
function JobBackend(metadataBackend, jobQueueProducer, jobPublisher, userIndexer) {
|
2016-01-08 22:47:59 +08:00
|
|
|
this.db = 5;
|
|
|
|
this.redisPrefix = 'batch:jobs:';
|
2015-12-22 02:57:10 +08:00
|
|
|
this.metadataBackend = metadataBackend;
|
2015-12-25 00:42:49 +08:00
|
|
|
this.jobQueueProducer = jobQueueProducer;
|
|
|
|
this.jobPublisher = jobPublisher;
|
|
|
|
this.userIndexer = userIndexer;
|
2015-12-22 02:57:10 +08:00
|
|
|
}
|
|
|
|
|
2015-12-25 00:42:49 +08:00
|
|
|
JobBackend.prototype.create = function (username, sql, host, callback) {
|
2015-12-22 02:57:10 +08:00
|
|
|
var self = this;
|
2015-12-24 00:29:11 +08:00
|
|
|
var job_id = uuid.v4();
|
|
|
|
var now = new Date().toISOString();
|
2015-12-22 02:57:10 +08:00
|
|
|
var redisParams = [
|
2015-12-29 22:46:04 +08:00
|
|
|
this.redisPrefix + job_id,
|
2015-12-22 02:57:10 +08:00
|
|
|
'user', username,
|
|
|
|
'status', 'pending',
|
|
|
|
'query', sql,
|
2015-12-24 00:29:11 +08:00
|
|
|
'created_at', now,
|
|
|
|
'updated_at', now
|
2015-12-22 02:57:10 +08:00
|
|
|
];
|
|
|
|
|
|
|
|
this.metadataBackend.redisCmd(this.db, 'HMSET', redisParams , function (err) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
2015-12-25 00:42:49 +08:00
|
|
|
self.jobQueueProducer.enqueue(job_id, host, function (err) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
// broadcast to consumers
|
|
|
|
self.jobPublisher.publish(host);
|
|
|
|
|
|
|
|
self.userIndexer.add(username, job_id, function (err) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
self.get(job_id, callback);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2015-12-31 22:42:31 +08:00
|
|
|
JobBackend.prototype.update = function (job_id, sql, callback) {
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
this.get(job_id, function (err, job) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (job.status !== 'pending') {
|
|
|
|
return callback(new Error('Job is not pending, it couldn\'t be updated'));
|
|
|
|
}
|
|
|
|
|
|
|
|
var now = new Date().toISOString();
|
|
|
|
var redisParams = [
|
|
|
|
self.redisPrefix + job_id,
|
|
|
|
'query', sql,
|
|
|
|
'updated_at', now
|
|
|
|
];
|
|
|
|
|
|
|
|
self.metadataBackend.redisCmd(self.db, 'HMSET', redisParams , function (err) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
self.get(job_id, callback);
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2015-12-25 00:42:49 +08:00
|
|
|
JobBackend.prototype.list = function (username, callback) {
|
|
|
|
var self = this;
|
2016-01-07 19:06:01 +08:00
|
|
|
|
2015-12-25 00:42:49 +08:00
|
|
|
this.userIndexer.list(username, function (err, job_ids) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
2016-01-07 19:06:01 +08:00
|
|
|
var initialLength = job_ids.length;
|
2015-12-25 00:42:49 +08:00
|
|
|
|
2016-01-07 19:06:01 +08:00
|
|
|
self._getCleanedList(username, job_ids, function (err, jobs) {
|
2015-12-25 00:42:49 +08:00
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
2016-01-05 02:08:13 +08:00
|
|
|
|
2016-01-07 19:06:01 +08:00
|
|
|
if (jobs.length < initialLength) {
|
|
|
|
return self.list(username, callback);
|
|
|
|
}
|
|
|
|
|
|
|
|
callback(null, jobs);
|
2015-12-25 00:42:49 +08:00
|
|
|
});
|
2015-12-22 02:57:10 +08:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2016-01-07 19:06:01 +08:00
|
|
|
JobBackend.prototype._getCleanedList = function (username, job_ids, callback) {
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
var jobsQueue = queue(job_ids.length);
|
|
|
|
|
|
|
|
job_ids.forEach(function(job_id) {
|
|
|
|
jobsQueue.defer(self._getIndexedJob.bind(self), job_id, username);
|
|
|
|
});
|
|
|
|
|
|
|
|
jobsQueue.awaitAll(function (err, jobs) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
callback(null, jobs.filter(function (job) {
|
|
|
|
return job ? true : false;
|
|
|
|
}));
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
JobBackend.prototype._getIndexedJob = function (job_id, username, callback) {
|
2016-01-05 02:08:13 +08:00
|
|
|
var self = this;
|
|
|
|
|
|
|
|
this.get(job_id, function (err, job) {
|
|
|
|
|
|
|
|
if (err && err.name === 'NotFoundError') {
|
|
|
|
return self.userIndexer.remove(username, job_id, function (err) {
|
|
|
|
if (err) {
|
|
|
|
console.error('Error removing key %s in user set', job_id, err);
|
|
|
|
}
|
|
|
|
callback();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
callback(null, job);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2016-01-22 19:43:41 +08:00
|
|
|
JobBackend.prototype._isJobFound = function (jobValues) {
|
|
|
|
return jobValues[0] && jobValues[1] && jobValues[2] && jobValues[3] && jobValues[4];
|
|
|
|
};
|
|
|
|
|
2015-12-24 00:29:11 +08:00
|
|
|
JobBackend.prototype.get = function (job_id, callback) {
|
2016-01-22 19:43:41 +08:00
|
|
|
var self = this;
|
2015-12-22 02:57:10 +08:00
|
|
|
var redisParams = [
|
2015-12-29 22:46:04 +08:00
|
|
|
this.redisPrefix + job_id,
|
2015-12-22 02:57:10 +08:00
|
|
|
'user',
|
|
|
|
'status',
|
|
|
|
'query',
|
|
|
|
'created_at',
|
2015-12-24 00:29:11 +08:00
|
|
|
'updated_at',
|
|
|
|
'failed_reason'
|
2015-12-22 02:57:10 +08:00
|
|
|
];
|
|
|
|
|
|
|
|
this.metadataBackend.redisCmd(this.db, 'HMGET', redisParams , function (err, jobValues) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
2016-01-22 19:43:41 +08:00
|
|
|
if (!self._isJobFound(jobValues)) {
|
2016-01-05 02:08:13 +08:00
|
|
|
var notFoundError = new Error('Job with id ' + job_id + ' not found');
|
|
|
|
notFoundError.name = 'NotFoundError';
|
|
|
|
return callback(notFoundError);
|
2015-12-22 02:57:10 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
callback(null, {
|
2015-12-24 00:29:11 +08:00
|
|
|
job_id: job_id,
|
2015-12-22 02:57:10 +08:00
|
|
|
user: jobValues[0],
|
|
|
|
status: jobValues[1],
|
|
|
|
query: jobValues[2],
|
|
|
|
created_at: jobValues[3],
|
2015-12-24 00:29:11 +08:00
|
|
|
updated_at: jobValues[4],
|
|
|
|
failed_reason: jobValues[5] ? jobValues[5] : undefined
|
2015-12-22 02:57:10 +08:00
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2016-01-08 22:47:59 +08:00
|
|
|
JobBackend.prototype.setRunning = function (job, callback) {
|
|
|
|
var now = new Date().toISOString();
|
2015-12-22 02:57:10 +08:00
|
|
|
var redisParams = [
|
2015-12-29 22:46:04 +08:00
|
|
|
this.redisPrefix + job.job_id,
|
2015-12-22 02:57:10 +08:00
|
|
|
'status', 'running',
|
2016-01-08 22:47:59 +08:00
|
|
|
'updated_at', now
|
2015-12-22 02:57:10 +08:00
|
|
|
];
|
|
|
|
|
|
|
|
this.metadataBackend.redisCmd(this.db, 'HMSET', redisParams, function (err) {
|
|
|
|
if (err) {
|
2016-01-08 22:47:59 +08:00
|
|
|
return callback(err);
|
2015-12-22 02:57:10 +08:00
|
|
|
}
|
|
|
|
|
2016-01-08 22:47:59 +08:00
|
|
|
job.status = 'running';
|
|
|
|
job.updated_at = now;
|
|
|
|
|
|
|
|
callback(null, job);
|
2015-12-22 02:57:10 +08:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2016-01-13 23:25:25 +08:00
|
|
|
JobBackend.prototype.setPending = function (job, callback) {
|
|
|
|
var now = new Date().toISOString();
|
|
|
|
var redisKey = this.redisPrefix + job.job_id;
|
|
|
|
var redisParams = [
|
|
|
|
redisKey,
|
|
|
|
'status', 'pending',
|
|
|
|
'updated_at', now
|
|
|
|
];
|
|
|
|
|
|
|
|
this.metadataBackend.redisCmd(this.db, 'HMSET', redisParams , function (err) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
job.status = 'pending';
|
|
|
|
job.updated_at = now;
|
|
|
|
|
|
|
|
callback(null, job);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2016-01-08 22:47:59 +08:00
|
|
|
JobBackend.prototype.setDone = function (job, callback) {
|
2015-12-22 02:57:10 +08:00
|
|
|
var self = this;
|
2016-01-08 22:47:59 +08:00
|
|
|
var now = new Date().toISOString();
|
2016-01-04 22:20:06 +08:00
|
|
|
var redisKey = this.redisPrefix + job.job_id;
|
2015-12-22 02:57:10 +08:00
|
|
|
var redisParams = [
|
2016-01-04 22:20:06 +08:00
|
|
|
redisKey,
|
2015-12-22 02:57:10 +08:00
|
|
|
'status', 'done',
|
2016-01-08 22:47:59 +08:00
|
|
|
'updated_at', now
|
2015-12-22 02:57:10 +08:00
|
|
|
];
|
|
|
|
|
|
|
|
this.metadataBackend.redisCmd(this.db, 'HMSET', redisParams , function (err) {
|
|
|
|
if (err) {
|
2016-01-08 22:47:59 +08:00
|
|
|
return callback(err);
|
2015-12-22 02:57:10 +08:00
|
|
|
}
|
|
|
|
|
2016-01-08 22:47:59 +08:00
|
|
|
self.metadataBackend.redisCmd(self.db, 'EXPIRE', [ redisKey, JOBS_TTL_IN_SECONDS ], function (err) {
|
2016-01-04 22:20:06 +08:00
|
|
|
if (err) {
|
2016-01-08 22:47:59 +08:00
|
|
|
return callback(err);
|
2016-01-04 22:20:06 +08:00
|
|
|
}
|
|
|
|
|
2016-01-08 22:47:59 +08:00
|
|
|
job.status = 'done';
|
|
|
|
job.updated_at = now;
|
|
|
|
|
|
|
|
callback(null, job);
|
2016-01-04 22:20:06 +08:00
|
|
|
});
|
2015-12-22 02:57:10 +08:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2016-01-08 23:24:53 +08:00
|
|
|
JobBackend.prototype.setFailed = function (job, error, callback) {
|
2015-12-22 02:57:10 +08:00
|
|
|
var self = this;
|
2016-01-08 22:47:59 +08:00
|
|
|
var now = new Date().toISOString();
|
2016-01-04 22:20:06 +08:00
|
|
|
var redisKey = this.redisPrefix + job.job_id;
|
2015-12-22 02:57:10 +08:00
|
|
|
var redisParams = [
|
2016-01-04 22:20:06 +08:00
|
|
|
redisKey,
|
2015-12-22 02:57:10 +08:00
|
|
|
'status', 'failed',
|
2016-01-08 23:24:53 +08:00
|
|
|
'failed_reason', error.message,
|
2016-01-08 22:47:59 +08:00
|
|
|
'updated_at', now
|
2015-12-22 02:57:10 +08:00
|
|
|
];
|
|
|
|
|
|
|
|
this.metadataBackend.redisCmd(this.db, 'HMSET', redisParams , function (err) {
|
|
|
|
if (err) {
|
2016-01-08 22:47:59 +08:00
|
|
|
return callback(err);
|
2015-12-22 02:57:10 +08:00
|
|
|
}
|
|
|
|
|
2016-01-08 22:47:59 +08:00
|
|
|
self.metadataBackend.redisCmd(self.db, 'EXPIRE', [ redisKey, JOBS_TTL_IN_SECONDS ], function (err) {
|
2016-01-04 22:20:06 +08:00
|
|
|
if (err) {
|
2016-01-08 22:47:59 +08:00
|
|
|
return callback(err);
|
2016-01-04 22:20:06 +08:00
|
|
|
}
|
|
|
|
|
2016-01-08 22:47:59 +08:00
|
|
|
job.status = 'failed';
|
|
|
|
job.updated_at = now;
|
2016-01-08 23:24:53 +08:00
|
|
|
job.failed_reason = error.message;
|
2016-01-08 22:47:59 +08:00
|
|
|
|
|
|
|
callback(null, job);
|
2016-01-04 22:20:06 +08:00
|
|
|
});
|
2015-12-22 02:57:10 +08:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2016-01-08 22:47:59 +08:00
|
|
|
JobBackend.prototype.setCancelled = function (job, callback) {
|
2015-12-31 03:16:18 +08:00
|
|
|
var self = this;
|
2016-01-08 22:47:59 +08:00
|
|
|
var now = new Date().toISOString();
|
2016-01-04 22:20:06 +08:00
|
|
|
var redisKey = this.redisPrefix + job.job_id;
|
2015-12-31 03:16:18 +08:00
|
|
|
var redisParams = [
|
2016-01-04 22:20:06 +08:00
|
|
|
redisKey,
|
2015-12-31 03:16:18 +08:00
|
|
|
'status', 'cancelled',
|
2016-01-08 22:47:59 +08:00
|
|
|
'updated_at', now
|
2015-12-31 03:16:18 +08:00
|
|
|
];
|
|
|
|
|
|
|
|
this.metadataBackend.redisCmd(this.db, 'HMSET', redisParams , function (err) {
|
|
|
|
if (err) {
|
2016-01-08 22:47:59 +08:00
|
|
|
return callback(err);
|
2015-12-31 03:16:18 +08:00
|
|
|
}
|
|
|
|
|
2016-01-13 23:25:25 +08:00
|
|
|
self.metadataBackend.redisCmd(self.db, 'PERSIST', [ redisKey ], function (err) {
|
2016-01-04 22:20:06 +08:00
|
|
|
if (err) {
|
2016-01-08 22:47:59 +08:00
|
|
|
return callback(err);
|
2016-01-04 22:20:06 +08:00
|
|
|
}
|
|
|
|
|
2016-01-08 22:47:59 +08:00
|
|
|
job.status = 'cancelled';
|
|
|
|
job.updated_at = now;
|
|
|
|
|
|
|
|
callback(null, job);
|
2016-01-04 22:20:06 +08:00
|
|
|
});
|
|
|
|
|
2015-12-31 03:16:18 +08:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2016-01-26 03:07:41 +08:00
|
|
|
JobBackend.prototype.setUnknown = function (job_id, callback) {
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
this.get(job_id, function (err, job) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
var now = new Date().toISOString();
|
|
|
|
var redisKey = self.redisPrefix + job.job_id;
|
|
|
|
var redisParams = [
|
|
|
|
redisKey,
|
|
|
|
'status', 'unknown',
|
|
|
|
'updated_at', now
|
|
|
|
];
|
|
|
|
|
|
|
|
self.metadataBackend.redisCmd(self.db, 'HMSET', redisParams , function (err) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
self.metadataBackend.redisCmd(self.db, 'EXPIRE', [ redisKey, JOBS_TTL_IN_SECONDS ], function (err) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
job.status = 'unknown';
|
|
|
|
job.updated_at = now;
|
|
|
|
|
|
|
|
callback(null, job);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2015-12-31 03:16:18 +08:00
|
|
|
|
2015-12-22 02:57:10 +08:00
|
|
|
module.exports = JobBackend;
|