Files
cloudron-box/src/platform.js
Girish Ramakrishnan b1b6f70118 Kill all tasks on shutdown and startup
BindsTo will kill all the tasks when systemctl stop box is executed.
But when restarted, it keeps the tasks running. Because of this behavior,
we kill the tasks on startup and stop of the box code.
2020-08-06 23:47:40 -07:00

166 lines
6.4 KiB
JavaScript

'use strict';
exports = module.exports = {
start: start,
stopAllTasks: stopAllTasks,
// exported for testing
_isReady: false
};
var addons = require('./addons.js'),
apps = require('./apps.js'),
assert = require('assert'),
async = require('async'),
debug = require('debug')('box:platform'),
fs = require('fs'),
graphs = require('./graphs.js'),
infra = require('./infra_version.js'),
locker = require('./locker.js'),
paths = require('./paths.js'),
reverseProxy = require('./reverseproxy.js'),
safe = require('safetydance'),
settings = require('./settings.js'),
sftp = require('./sftp.js'),
shell = require('./shell.js'),
tasks = require('./tasks.js'),
_ = require('underscore');
var NOOP_CALLBACK = function (error) { if (error) debug(error); };
function start(callback) {
assert.strictEqual(typeof callback, 'function');
if (process.env.BOX_ENV === 'test' && !process.env.TEST_CREATE_INFRA) return callback();
debug('initializing addon infrastructure');
var existingInfra = { version: 'none' };
if (fs.existsSync(paths.INFRA_VERSION_FILE)) {
existingInfra = safe.JSON.parse(fs.readFileSync(paths.INFRA_VERSION_FILE, 'utf8'));
if (!existingInfra) existingInfra = { version: 'corrupt' };
}
// short-circuit for the restart case
if (_.isEqual(infra, existingInfra)) {
debug('platform is uptodate at version %s', infra.version);
onPlatformReady();
return callback();
}
debug('Updating infrastructure from %s to %s', existingInfra.version, infra.version);
var error = locker.lock(locker.OP_PLATFORM_START);
if (error) return callback(error);
async.series([
(next) => { if (existingInfra.version !== infra.version) removeAllContainers(existingInfra, next); else next(); },
markApps.bind(null, existingInfra), // mark app state before we start addons. this gives the db import logic a chance to mark an app as errored
graphs.startGraphite.bind(null, existingInfra),
sftp.startSftp.bind(null, existingInfra),
addons.startServices.bind(null, existingInfra),
fs.writeFile.bind(fs, paths.INFRA_VERSION_FILE, JSON.stringify(infra, null, 4))
], function (error) {
if (error) return callback(error);
locker.unlock(locker.OP_PLATFORM_START);
onPlatformReady();
callback();
});
}
function stopAllTasks(callback) {
tasks.stopAllTasks(callback);
}
function onPlatformReady() {
debug('onPlatformReady: platform is ready');
exports._isReady = true;
apps.schedulePendingTasks(NOOP_CALLBACK);
applyPlatformConfig(NOOP_CALLBACK);
pruneInfraImages(NOOP_CALLBACK);
}
function applyPlatformConfig(callback) {
// scale back db containers, if possible. this is retried because updating memory constraints can fail
// with failed to write to memory.memsw.limit_in_bytes: write /sys/fs/cgroup/memory/docker/xx/memory.memsw.limit_in_bytes: device or resource busy
async.retry({ times: 10, interval: 5 * 60 * 1000 }, function (retryCallback) {
settings.getPlatformConfig(function (error, platformConfig) {
if (error) return retryCallback(error);
addons.updateServiceConfig(platformConfig, function (error) {
if (error) debug('Error updating services. Will rety in 5 minutes', platformConfig, error);
retryCallback(error);
});
});
}, callback);
}
function pruneInfraImages(callback) {
debug('pruneInfraImages: checking existing images');
// cannot blindly remove all unused images since redis image may not be used
const images = infra.baseImages.concat(Object.keys(infra.images).map(function (addon) { return infra.images[addon]; }));
async.eachSeries(images, function (image, iteratorCallback) {
let output = safe.child_process.execSync(`docker images --digests ${image.repo} --format "{{.ID}} {{.Repository}}:{{.Tag}}@{{.Digest}}"`, { encoding: 'utf8' });
if (output === null) return iteratorCallback(safe.error);
let lines = output.trim().split('\n');
for (let line of lines) {
if (!line) continue;
let parts = line.split(' '); // [ ID, Repo:Tag@Digest ]
if (image.tag === parts[1]) continue; // keep
debug(`pruneInfraImages: removing unused image of ${image.repo}: tag: ${parts[1]} id: ${parts[0]}`);
let result = safe.child_process.execSync(`docker rmi ${parts[0]}`, { encoding: 'utf8' });
if (result === null) debug(`Erroring removing image ${parts[0]}: ${safe.error.mesage}`);
}
iteratorCallback();
}, callback);
}
function removeAllContainers(existingInfra, callback) {
debug('removeAllContainers: removing all containers for infra upgrade');
async.series([
shell.exec.bind(null, 'removeAllContainers', 'docker ps -qa --filter \'label=isCloudronManaged\' | xargs --no-run-if-empty docker stop'),
shell.exec.bind(null, 'removeAllContainers', 'docker ps -qa --filter \'label=isCloudronManaged\' | xargs --no-run-if-empty docker rm -f')
], callback);
}
function markApps(existingInfra, callback) {
if (existingInfra.version === 'none') { // cloudron is being restored from backup
debug('markApps: restoring installed apps');
apps.restoreInstalledApps(callback);
} else if (existingInfra.version !== infra.version) {
debug('markApps: reconfiguring installed apps');
reverseProxy.removeAppConfigs(); // should we change the cert location, nginx will not start
apps.configureInstalledApps(callback);
} else {
let changedAddons = [];
if (infra.images.mysql.tag !== existingInfra.images.mysql.tag) changedAddons.push('mysql');
if (infra.images.postgresql.tag !== existingInfra.images.postgresql.tag) changedAddons.push('postgresql');
if (infra.images.mongodb.tag !== existingInfra.images.mongodb.tag) changedAddons.push('mongodb');
if (infra.images.redis.tag !== existingInfra.images.redis.tag) changedAddons.push('redis');
if (changedAddons.length) {
// restart apps if docker image changes since the IP changes and any "persistent" connections fail
debug(`markApps: changedAddons: ${JSON.stringify(changedAddons)}`);
apps.restartAppsUsingAddons(changedAddons, callback);
} else {
debug('markApps: apps are already uptodate');
callback();
}
}
}