Files
cloudron-box/src/tasks.js
T

187 lines
6.4 KiB
JavaScript
Raw Normal View History

2018-11-16 11:13:03 -08:00
'use strict';
exports = module.exports = {
2018-11-30 14:16:00 -08:00
get: get,
2018-12-08 18:50:06 -08:00
update: update,
2018-12-08 20:12:23 -08:00
listPaged: listPaged,
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-12-08 18:50:06 -08:00
// task types
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); };
2018-12-08 18:50:06 -08:00
const TASKS = { // indexed by task type
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
}
};
2018-12-08 18:50:06 -08:00
let gTasks = {}; // indexed by task id
2018-11-29 16:13:01 -08:00
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 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));
progress.active = !!gTasks[id];
2018-11-16 11:13:03 -08:00
callback(null, progress);
});
}
2018-12-08 18:50:06 -08:00
function update(id, task, callback) {
2018-11-29 15:16:31 -08:00
assert.strictEqual(typeof id, 'string');
2018-12-08 18:50:06 -08:00
assert.strictEqual(typeof task, 'object');
2018-11-29 15:16:31 -08:00
assert.strictEqual(typeof callback, 'function');
2018-12-08 18:50:06 -08:00
debug(`${id}: ${JSON.stringify(task)}`);
taskdb.update(id, task, function (error) {
if (error && error.reason == DatabaseError.NOT_FOUND) return callback(new TaskError(TaskError.NOT_FOUND));
if (error) return callback(new TaskError(TaskError.INTERNAL_ERROR, error));
callback();
});
2018-11-29 15:16:31 -08:00
}
2018-12-08 18:50:06 -08:00
function startTask(type, args, auditSource, callback) {
assert.strictEqual(typeof type, 'string');
assert(args && typeof args === 'object');
2018-11-16 11:13:03 -08:00
assert.strictEqual(typeof auditSource, 'object');
assert.strictEqual(typeof callback, 'function');
2018-12-08 18:50:06 -08:00
const taskInfo = TASKS[type];
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
2018-12-08 18:50:06 -08:00
taskdb.add({ type: type, percent: 0, message: 'Starting', args: args }, function (error, taskId) {
if (error) return callback(new TaskError(TaskError.INTERNAL_ERROR, error));
2018-11-29 16:13:01 -08:00
2018-12-08 18:50:06 -08:00
debug(`startTask - starting task ${type}. logs at ${taskInfo.logFile}. id ${taskId}`);
2018-11-29 16:13:01 -08:00
2018-12-08 18:50:06 -08:00
eventlog.add(taskInfo.startEventId, auditSource, args);
2018-11-29 16:13:01 -08:00
2018-12-08 18:50:06 -08:00
gTasks[taskId] = child_process.fork(taskInfo.program, [ taskId ], { stdio: [ 'pipe', fd, fd, 'ipc' ]}); // fork requires ipc
gTasks[taskId].once('exit', function (code, signal) {
debug(`startTask: ${taskId} completed with code ${code} and signal ${signal}`);
2018-11-29 16:13:01 -08:00
2018-12-08 18:50:06 -08:00
get(taskId, function (error, task) {
if (!error && task.percent !== 100) { // task crashed or was killed by us (code 50)
error = code === 0 ? new Error(`${taskId} task stopped`) : new Error(`${taskId} task crashed with code ${code} and signal ${signal}`);
update(taskId, { percent: 100, errorMessage: error.message }, NOOP_CALLBACK);
} else if (!error && task.errorMessage) {
error = new Error(task.errorMessage);
}
2018-11-30 16:00:47 -08:00
2018-12-08 18:50:06 -08:00
eventlog.add(taskInfo.finishEventId, auditSource, _.extend({ errorMessage: error ? error.message : null }, task ? task.result : {}));
2018-11-29 16:13:01 -08:00
2018-12-08 18:50:06 -08:00
locker.unlock(taskInfo.lock);
2018-11-29 16:13:01 -08:00
2018-12-08 18:50:06 -08:00
if (error) taskInfo.onFailure(error);
2018-11-29 16:13:01 -08:00
2018-12-08 18:50:06 -08:00
gTasks[taskId] = null;
2018-11-29 16:13:01 -08:00
2018-12-08 18:50:06 -08:00
debug(`startTask: ${taskId} done`);
});
2018-11-29 16:13:01 -08:00
});
2018-12-08 18:50:06 -08:00
callback(null, taskId);
});
2018-11-29 16:13:01 -08:00
}
function stopTask(id, auditSource, callback) {
assert.strictEqual(typeof id, 'string');
assert.strictEqual(typeof auditSource, 'object');
assert.strictEqual(typeof callback, 'function');
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
}
2018-12-08 20:12:23 -08:00
function listPaged(type, page, perPage, callback) {
assert(typeof type === 'string' || type === null);
assert.strictEqual(typeof page, 'number');
assert.strictEqual(typeof perPage, 'number');
assert.strictEqual(typeof callback, 'function');
taskdb.listPaged(type, page, perPage, function (error, tasks) {
if (error) return callback(new TaskError(TaskError.INTERNAL_ERROR, error));
callback(null, tasks);
});
}