2015-10-18 08:40:24 -07:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
exports = module.exports = {
|
|
|
|
|
sync: sync
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var appdb = require('./appdb.js'),
|
|
|
|
|
apps = require('./apps.js'),
|
|
|
|
|
assert = require('assert'),
|
2015-10-19 18:27:18 -07:00
|
|
|
async = require('async'),
|
2015-10-18 08:40:24 -07:00
|
|
|
CronJob = require('cron').CronJob,
|
|
|
|
|
debug = require('debug')('box:src/scheduler'),
|
|
|
|
|
docker = require('./docker.js').connection,
|
2015-10-19 22:41:42 -07:00
|
|
|
paths = require('./paths.js'),
|
|
|
|
|
safe = require('safetydance'),
|
2015-10-18 08:40:24 -07:00
|
|
|
_ = require('underscore');
|
|
|
|
|
|
|
|
|
|
var NOOP_CALLBACK = function (error) { if (error) console.error(error); };
|
|
|
|
|
|
2015-10-19 22:41:42 -07:00
|
|
|
// appId -> { tasksConfig (manifest), jobs -> { containerId, cronJob } }
|
|
|
|
|
function loadState() {
|
|
|
|
|
var tasks = safe.JSON.parse(safe.fs.readFileSync(paths.SCHEDULER_FILE, 'utf8'));
|
|
|
|
|
return tasks || { };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function saveState(tasks) {
|
|
|
|
|
safe.fs.writeFileSync(paths.SCHEDULER_FILE, JSON.stringify(tasks, null, 4), 'utf8');
|
|
|
|
|
}
|
2015-10-18 08:40:24 -07:00
|
|
|
|
|
|
|
|
function sync(callback) {
|
|
|
|
|
assert(!callback || typeof callback === 'function');
|
|
|
|
|
|
|
|
|
|
callback = callback || NOOP_CALLBACK;
|
|
|
|
|
|
|
|
|
|
debug('Syncing');
|
|
|
|
|
|
2015-10-19 22:41:42 -07:00
|
|
|
var state = loadState();
|
|
|
|
|
|
2015-10-18 08:40:24 -07:00
|
|
|
apps.getAll(function (error, allApps) {
|
|
|
|
|
if (error) return callback(error);
|
|
|
|
|
|
|
|
|
|
// stop tasks of apps that went away
|
|
|
|
|
var allAppIds = allApps.map(function (app) { return app.id; });
|
2015-10-19 22:41:42 -07:00
|
|
|
var removedAppIds = _.difference(Object.keys(state), allAppIds);
|
|
|
|
|
async.eachSeries(removedAppIds, function (appId, iteratorDone) {
|
|
|
|
|
stopJobs(appId, state[appId], iteratorDone);
|
|
|
|
|
}, function (error) {
|
2015-10-19 19:04:53 -07:00
|
|
|
if (error) debug('Error stopping jobs : %j', error);
|
2015-10-18 08:40:24 -07:00
|
|
|
|
2015-10-19 22:41:42 -07:00
|
|
|
state = _.omit(state, removedAppIds);
|
|
|
|
|
|
2015-10-19 19:04:53 -07:00
|
|
|
// start tasks of new apps
|
|
|
|
|
allApps.forEach(function (app) {
|
2015-10-19 22:41:42 -07:00
|
|
|
state[app.id] = resetAppState(app.id, state[app.id] || null, app.manifest.addons.scheduler || null);
|
2015-10-19 19:04:53 -07:00
|
|
|
});
|
|
|
|
|
|
2015-10-19 22:41:42 -07:00
|
|
|
saveState(state);
|
|
|
|
|
|
2015-10-19 19:04:53 -07:00
|
|
|
debug('Done syncing');
|
|
|
|
|
});
|
2015-10-18 08:40:24 -07:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-19 22:41:42 -07:00
|
|
|
function stopJobs(appId, appState, callback) {
|
2015-10-18 08:40:24 -07:00
|
|
|
assert.strictEqual(typeof appId, 'string');
|
|
|
|
|
|
|
|
|
|
debug('stopJobs for %s', appId);
|
|
|
|
|
|
2015-10-19 22:41:42 -07:00
|
|
|
async.eachSeries(Object.keys(appState.jobs), function (taskName, iteratorDone) {
|
|
|
|
|
appState.jobs[taskName].cronJob.stop();
|
|
|
|
|
killTask(appState.jobs[taskName].containerId, iteratorDone);
|
|
|
|
|
}, callback);
|
2015-10-18 08:40:24 -07:00
|
|
|
}
|
|
|
|
|
|
2015-10-19 19:04:53 -07:00
|
|
|
function createCronJobs(appId, tasksConfig) {
|
|
|
|
|
debug('creating cron jobs for %s', appId);
|
2015-10-18 08:40:24 -07:00
|
|
|
|
2015-10-19 22:41:42 -07:00
|
|
|
var jobs = { };
|
|
|
|
|
|
2015-10-19 16:29:28 -07:00
|
|
|
Object.keys(tasksConfig).forEach(function (taskName) {
|
|
|
|
|
var task = tasksConfig[taskName];
|
2015-10-18 08:40:24 -07:00
|
|
|
|
|
|
|
|
debug('scheduling task %s/%s @ 00 %s : %s', appId, taskName, task.schedule, task.command);
|
|
|
|
|
|
2015-10-19 22:41:42 -07:00
|
|
|
var cronJob = new CronJob({
|
2015-10-18 08:40:24 -07:00
|
|
|
cronTime: '00 ' + task.schedule, // at this point, the pattern has been validated
|
2015-10-19 22:41:42 -07:00
|
|
|
onTick: doTask.bind(null, appId, taskName),
|
2015-10-18 08:40:24 -07:00
|
|
|
start: true
|
|
|
|
|
});
|
|
|
|
|
|
2015-10-19 22:41:42 -07:00
|
|
|
jobs[taskName] = { cronJob: cronJob, containerId: null };
|
2015-10-18 08:40:24 -07:00
|
|
|
});
|
2015-10-19 22:41:42 -07:00
|
|
|
|
|
|
|
|
return jobs;
|
2015-10-18 08:40:24 -07:00
|
|
|
}
|
|
|
|
|
|
2015-10-19 22:41:42 -07:00
|
|
|
function resetAppState(appId, appState, tasksConfig) {
|
2015-10-18 08:40:24 -07:00
|
|
|
assert.strictEqual(typeof appId, 'string');
|
2015-10-19 22:41:42 -07:00
|
|
|
assert.strictEqual(typeof appState, 'object');
|
|
|
|
|
assert.strictEqual(typeof tasksConfig, 'object');
|
2015-10-18 08:40:24 -07:00
|
|
|
|
2015-10-19 22:41:42 -07:00
|
|
|
if (appState) {
|
|
|
|
|
// cleanup existing state
|
|
|
|
|
if (_.isEqual(appState.tasksConfig, tasksConfig)) return; // nothing changed
|
2015-10-18 08:40:24 -07:00
|
|
|
|
2015-10-19 22:41:42 -07:00
|
|
|
stopJobs(appId, appState); // something changed, stop all the existing jobs
|
2015-10-18 08:40:24 -07:00
|
|
|
}
|
|
|
|
|
|
2015-10-19 22:41:42 -07:00
|
|
|
if (!tasksConfig) return null;
|
2015-10-18 08:40:24 -07:00
|
|
|
|
2015-10-19 22:41:42 -07:00
|
|
|
return {
|
|
|
|
|
tasksConfig: tasksConfig,
|
|
|
|
|
jobs: createCronJobs(appId, tasksConfig)
|
|
|
|
|
};
|
2015-10-19 19:04:53 -07:00
|
|
|
}
|
|
|
|
|
|
2015-10-19 22:41:42 -07:00
|
|
|
function killTask(containerId, callback) {
|
2015-10-19 19:04:53 -07:00
|
|
|
if (!containerId) return callback();
|
|
|
|
|
|
|
|
|
|
async.series([
|
|
|
|
|
docker.stopContainer.bind(null, containerId),
|
|
|
|
|
docker.deleteContainer.bind(null, containerId)
|
|
|
|
|
], callback);
|
2015-10-18 08:40:24 -07:00
|
|
|
}
|
|
|
|
|
|
2015-10-19 22:41:42 -07:00
|
|
|
function doTask(appId, taskName, callback) {
|
2015-10-18 08:40:24 -07:00
|
|
|
assert.strictEqual(typeof appId, 'string');
|
|
|
|
|
assert.strictEqual(typeof taskName, 'string');
|
|
|
|
|
assert(!callback || typeof callback === 'function');
|
|
|
|
|
|
|
|
|
|
callback = callback || NOOP_CALLBACK;
|
|
|
|
|
|
2015-10-19 22:41:42 -07:00
|
|
|
var state = loadState();
|
|
|
|
|
var job = state[appId].jobs[taskName];
|
2015-10-18 08:40:24 -07:00
|
|
|
|
2015-10-19 22:41:42 -07:00
|
|
|
if (job.containerId) {
|
2015-10-19 19:04:53 -07:00
|
|
|
debug('task %s/%s is already running. killing it');
|
2015-10-19 22:41:42 -07:00
|
|
|
return killTask(job.containerId, callback);
|
2015-10-18 08:40:24 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
apps.get(appId, function (error, app) {
|
|
|
|
|
if (error) return callback(error);
|
|
|
|
|
|
|
|
|
|
if (app.installationState !== appdb.ISTATE_INSTALLED || app.runState !== appdb.RSTATE_RUNNING) {
|
|
|
|
|
debug('task %s skipped. app %s is not installed/running', taskName, app.id);
|
|
|
|
|
return callback();
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-19 18:27:18 -07:00
|
|
|
debug('task %s/%s starting', app.id, taskName);
|
2015-10-18 08:40:24 -07:00
|
|
|
|
2015-10-19 22:41:42 -07:00
|
|
|
docker.createSubcontainer(app, [ '/bin/sh', '-c', state[appId].tasksConfig[taskName].command ], function (error, container) {
|
|
|
|
|
job.containerId = container.id;
|
|
|
|
|
|
|
|
|
|
saveState(state);
|
2015-10-18 08:40:24 -07:00
|
|
|
|
2015-10-19 18:27:18 -07:00
|
|
|
docker.startContainer(container.id, callback);
|
2015-10-18 08:40:24 -07:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|