2018-11-16 11:13:03 -08:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
exports = module.exports = {
|
2018-11-30 14:16:00 -08:00
|
|
|
update: update,
|
|
|
|
|
get: get,
|
2018-11-16 11:13:03 -08:00
|
|
|
|
2018-11-29 16:13:01 -08:00
|
|
|
startTask: startTask,
|
2018-11-16 11:13:03 -08:00
|
|
|
stopTask: stopTask,
|
|
|
|
|
|
|
|
|
|
TaskError: TaskError,
|
|
|
|
|
|
2018-11-19 20:01:02 -08:00
|
|
|
TASK_BACKUP: 'backup',
|
|
|
|
|
TASK_UPDATE: 'update',
|
|
|
|
|
TASK_MIGRATE: 'migrate'
|
2018-11-16 11:13:03 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let assert = require('assert'),
|
2018-11-29 16:13:01 -08:00
|
|
|
child_process = require('child_process'),
|
2018-11-16 11:13:03 -08:00
|
|
|
DatabaseError = require('./databaseerror.js'),
|
|
|
|
|
debug = require('debug')('box:tasks'),
|
2018-11-29 16:13:01 -08:00
|
|
|
eventlog = require('./eventlog.js'),
|
|
|
|
|
locker = require('./locker.js'),
|
|
|
|
|
mailer = require('./mailer.js'),
|
|
|
|
|
paths = require('./paths.js'),
|
|
|
|
|
safe = require('safetydance'),
|
2018-11-16 11:13:03 -08:00
|
|
|
taskdb = require('./taskdb.js'),
|
2018-11-30 16:00:47 -08:00
|
|
|
util = require('util'),
|
|
|
|
|
_ = require('underscore');
|
2018-11-16 11:13:03 -08:00
|
|
|
|
2018-11-29 16:13:01 -08:00
|
|
|
const NOOP_CALLBACK = function (error) { if (error) debug(error); };
|
|
|
|
|
|
|
|
|
|
const TASKS = {
|
2018-11-30 16:00:47 -08:00
|
|
|
backup: {
|
2018-11-29 16:13:01 -08:00
|
|
|
lock: locker.OP_FULL_BACKUP,
|
|
|
|
|
logFile: paths.BACKUP_LOG_FILE,
|
|
|
|
|
program: __dirname + '/tasks/backuptask.js',
|
|
|
|
|
onFailure: mailer.backupFailed,
|
|
|
|
|
startEventId: eventlog.ACTION_BACKUP_START,
|
|
|
|
|
finishEventId: eventlog.ACTION_BACKUP_FINISH
|
2018-11-30 16:00:47 -08:00
|
|
|
},
|
|
|
|
|
update: {
|
|
|
|
|
lock: locker.OP_BOX_UPDATE,
|
|
|
|
|
logFile: paths.UPDATER_LOG_FILE,
|
|
|
|
|
program: __dirname + '/tasks/updatertask.js',
|
|
|
|
|
onFailure: NOOP_CALLBACK,
|
|
|
|
|
startEventId: eventlog.ACTION_UPDATE,
|
|
|
|
|
finishEventId: eventlog.ACTION_UPDATE
|
2018-11-29 16:13:01 -08:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let gTasks = {};
|
|
|
|
|
|
2018-11-16 11:13:03 -08:00
|
|
|
function TaskError(reason, errorOrMessage) {
|
|
|
|
|
assert.strictEqual(typeof reason, 'string');
|
|
|
|
|
assert(errorOrMessage instanceof Error || typeof errorOrMessage === 'string' || typeof errorOrMessage === 'undefined');
|
|
|
|
|
|
|
|
|
|
Error.call(this);
|
|
|
|
|
Error.captureStackTrace(this, this.constructor);
|
|
|
|
|
|
|
|
|
|
this.name = this.constructor.name;
|
|
|
|
|
this.reason = reason;
|
|
|
|
|
if (typeof errorOrMessage === 'undefined') {
|
|
|
|
|
this.message = reason;
|
|
|
|
|
} else if (typeof errorOrMessage === 'string') {
|
|
|
|
|
this.message = errorOrMessage;
|
|
|
|
|
} else {
|
|
|
|
|
this.message = 'Internal error';
|
|
|
|
|
this.nestedError = errorOrMessage;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
util.inherits(TaskError, Error);
|
|
|
|
|
TaskError.INTERNAL_ERROR = 'Internal Error';
|
|
|
|
|
TaskError.BAD_STATE = 'Bad State';
|
|
|
|
|
TaskError.NOT_FOUND = 'Not Found';
|
|
|
|
|
|
2018-11-30 14:16:00 -08:00
|
|
|
function update(id, progress, callback) {
|
2018-11-16 11:13:03 -08:00
|
|
|
assert.strictEqual(typeof id, 'string');
|
|
|
|
|
assert.strictEqual(typeof progress, 'object');
|
|
|
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
|
|
|
|
|
|
debug(`${id}: ${JSON.stringify(progress)}`);
|
|
|
|
|
|
2018-11-30 14:16:00 -08:00
|
|
|
taskdb.update(id, progress, function (error) {
|
2018-11-16 11:13:03 -08:00
|
|
|
if (error) return callback(new TaskError(TaskError.INTERNAL_ERROR, error));
|
|
|
|
|
|
|
|
|
|
callback();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-30 14:16:00 -08:00
|
|
|
function get(id, callback) {
|
2018-11-16 11:13:03 -08:00
|
|
|
assert.strictEqual(typeof id, 'string');
|
|
|
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
|
|
2018-11-30 14:16:00 -08:00
|
|
|
taskdb.get(id, function (error, progress) {
|
2018-11-16 11:13:03 -08:00
|
|
|
if (error && error.reason == DatabaseError.NOT_FOUND) return callback(new TaskError(TaskError.NOT_FOUND));
|
|
|
|
|
if (error) return callback(new TaskError(TaskError.INTERNAL_ERROR, error));
|
|
|
|
|
|
2018-11-29 23:12:03 -08:00
|
|
|
progress.active = !!gTasks[id];
|
|
|
|
|
|
2018-11-16 11:13:03 -08:00
|
|
|
callback(null, progress);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-30 16:00:47 -08:00
|
|
|
function clear(id, args, callback) {
|
2018-11-29 15:16:31 -08:00
|
|
|
assert.strictEqual(typeof id, 'string');
|
2018-11-30 16:00:47 -08:00
|
|
|
assert(args && typeof args === 'object');
|
2018-11-29 15:16:31 -08:00
|
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
|
|
2018-11-30 16:00:47 -08:00
|
|
|
update(id, { percent: 0, message: 'Starting', result: '', errorMessage: '', args: args }, callback);
|
2018-11-29 15:16:31 -08:00
|
|
|
}
|
|
|
|
|
|
2018-11-29 23:28:26 -08:00
|
|
|
function startTask(id, args, auditSource, callback) {
|
2018-11-16 11:13:03 -08:00
|
|
|
assert.strictEqual(typeof id, 'string');
|
2018-11-30 14:57:24 -08:00
|
|
|
assert(args && typeof args === 'object');
|
2018-11-16 11:13:03 -08:00
|
|
|
assert.strictEqual(typeof auditSource, 'object');
|
|
|
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
|
|
2018-11-29 23:10:15 -08:00
|
|
|
const taskInfo = TASKS[id];
|
2018-11-29 16:13:01 -08:00
|
|
|
if (!taskInfo) return callback(new TaskError(TaskError.NOT_FOUND, 'No such task'));
|
2018-11-16 11:13:03 -08:00
|
|
|
|
2018-11-29 16:13:01 -08:00
|
|
|
let error = locker.lock(taskInfo.lock);
|
|
|
|
|
if (error) return callback(new TaskError(TaskError.BAD_STATE, error.message));
|
2018-11-16 11:13:03 -08:00
|
|
|
|
2018-11-29 16:13:01 -08:00
|
|
|
let fd = safe.fs.openSync(taskInfo.logFile, 'a'); // will autoclose
|
|
|
|
|
if (!fd) {
|
|
|
|
|
debug(`startTask: unable to get log filedescriptor ${safe.error.message}`);
|
|
|
|
|
locker.unlock(taskInfo.lock);
|
|
|
|
|
return callback(new TaskError(TaskError.INTERNAL_ERROR, error.message));
|
2018-11-16 11:13:03 -08:00
|
|
|
}
|
2018-11-29 16:13:01 -08:00
|
|
|
|
|
|
|
|
debug(`startTask - starting task ${id}. logs at ${taskInfo.logFile}`);
|
|
|
|
|
|
|
|
|
|
// when parent process dies, this process is killed because KillMode=control-group in systemd unit file
|
|
|
|
|
assert(!gTasks[id], 'Task is already running');
|
|
|
|
|
|
2018-11-30 16:00:47 -08:00
|
|
|
clear(id, args, NOOP_CALLBACK);
|
2018-11-30 14:57:24 -08:00
|
|
|
eventlog.add(taskInfo.startEventId, auditSource, args);
|
2018-11-29 16:13:01 -08:00
|
|
|
|
2018-11-30 16:00:47 -08:00
|
|
|
gTasks[id] = child_process.fork(taskInfo.program, [], { stdio: [ 'pipe', fd, fd, 'ipc' ]}); // fork requires ipc
|
2018-11-29 16:13:01 -08:00
|
|
|
gTasks[id].once('exit', function (code, signal) {
|
|
|
|
|
debug(`startTask: ${id} completed with code ${code} and signal ${signal}`);
|
|
|
|
|
|
2018-11-30 14:16:00 -08:00
|
|
|
get(id, function (error, progress) {
|
2018-11-30 16:00:47 -08:00
|
|
|
if (!error && progress.percent !== 100) { // task crashed or was killed by us (code 50)
|
|
|
|
|
error = code === 0 ? new Error(`${id} task stopped`) : new Error(`${id} task crashed with code ${code} and signal ${signal}`);
|
|
|
|
|
update(id, { percent: 100, errorMessage: error.message }, NOOP_CALLBACK);
|
|
|
|
|
} else if (!error && progress.errorMessage) {
|
|
|
|
|
error = new Error(progress.errorMessage);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
eventlog.add(taskInfo.finishEventId, auditSource, _.extend({ errorMessage: error ? error.message : null }, progress ? progress.result : {}));
|
2018-11-29 16:13:01 -08:00
|
|
|
|
|
|
|
|
locker.unlock(taskInfo.lock);
|
|
|
|
|
|
|
|
|
|
if (error) taskInfo.onFailure(error);
|
|
|
|
|
|
|
|
|
|
gTasks[id] = null;
|
|
|
|
|
|
|
|
|
|
debug(`startTask: ${id} done`);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
callback(null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function stopTask(id, auditSource, callback) {
|
|
|
|
|
assert.strictEqual(typeof id, 'string');
|
|
|
|
|
assert.strictEqual(typeof auditSource, 'object');
|
|
|
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
|
|
2018-11-29 23:10:15 -08:00
|
|
|
const taskInfo = TASKS[id];
|
|
|
|
|
if (!taskInfo) return callback(new TaskError(TaskError.NOT_FOUND, 'No such task'));
|
|
|
|
|
|
2018-11-29 16:13:01 -08:00
|
|
|
if (!gTasks[id]) return callback(new TaskError(TaskError.BAD_STATE, 'task is not active'));
|
|
|
|
|
|
|
|
|
|
debug(`stopTask: stopping task ${id}`);
|
|
|
|
|
|
|
|
|
|
gTasks[id].kill('SIGTERM'); // this will end up calling the 'exit' signal handler
|
|
|
|
|
|
|
|
|
|
callback(null);
|
2018-11-16 11:13:03 -08:00
|
|
|
}
|