diff --git a/src/addons.js b/src/addons.js index 9905ae8ed..ca0d1c28a 100644 --- a/src/addons.js +++ b/src/addons.js @@ -3,11 +3,11 @@ exports = module.exports = { AddonsError: AddonsError, - getAddons: getAddons, - getAddon: getAddon, - configureAddon: configureAddon, + getServices: getServices, + getService: getService, + configureService: configureService, getLogs: getLogs, - restartAddon: restartAddon, + restartService: restartService, startAddons: startAddons, updateAddonConfig: updateAddonConfig, @@ -85,7 +85,6 @@ function AddonsError(reason, errorOrMessage) { } util.inherits(AddonsError, Error); AddonsError.INTERNAL_ERROR = 'Internal Error'; -AddonsError.NOT_SUPPORTED = 'Not Supported'; AddonsError.NOT_FOUND = 'Not Found'; AddonsError.NOT_ACTIVE = 'Not Active'; @@ -93,14 +92,6 @@ const NOOP = function (app, options, callback) { return callback(); }; const NOOP_CALLBACK = function (error) { if (error) debug(error); }; const RMADDONDIR_CMD = path.join(__dirname, 'scripts/rmaddondir.sh'); -// TODO: maybe derive these defaults based on how many apps are using them -const DEFAULT_MEMORY_LIMITS = { - mysql: (1 + Math.round(os.totalmem()/(1024*1024*1024)/4)) * 256 * 1024 * 1024, - mongodb: (1 + Math.round(os.totalmem()/(1024*1024*1024)/4)) * 200 * 1024 * 1024, - postgresql: (1 + Math.round(os.totalmem()/(1024*1024*1024)/4)) * 256 * 1024 * 1024, - mail: Math.max((1 + Math.round(os.totalmem()/(1024*1024*1024)/4)) * 128, 256) * 1024 * 1024 -}; - // setup can be called multiple times for the same app (configure crash restart) and existing data must not be lost // teardown is destructive. app data stored with the addon is lost var KNOWN_ADDONS = { @@ -110,8 +101,6 @@ var KNOWN_ADDONS = { backup: NOOP, restore: setupEmail, clear: NOOP, - status: statusContainerAddon.bind(null, 'mail', 'CLOUDRON_MAIL_TOKEN'), - restart: restartContainerAddon.bind(null, 'email') }, ldap: { setup: setupLdap, @@ -119,8 +108,6 @@ var KNOWN_ADDONS = { backup: NOOP, restore: setupLdap, clear: NOOP, - status: null, - restart: null }, localstorage: { setup: setupLocalStorage, // docker creates the directory for us @@ -128,8 +115,6 @@ var KNOWN_ADDONS = { backup: NOOP, // no backup because it's already inside app data restore: NOOP, clear: clearLocalStorage, - status: null, - restart: null }, mongodb: { setup: setupMongoDb, @@ -137,8 +122,6 @@ var KNOWN_ADDONS = { backup: backupMongoDb, restore: restoreMongoDb, clear: clearMongodb, - status: statusContainerAddon.bind(null, 'mongodb', 'CLOUDRON_MONGODB_TOKEN'), - restart: restartContainerAddon.bind(null, 'mongodb') }, mysql: { setup: setupMySql, @@ -146,8 +129,6 @@ var KNOWN_ADDONS = { backup: backupMySql, restore: restoreMySql, clear: clearMySql, - status: statusContainerAddon.bind(null, 'mysql', 'CLOUDRON_MYSQL_TOKEN'), - restart: restartContainerAddon.bind(null, 'mysql') }, oauth: { setup: setupOauth, @@ -155,8 +136,6 @@ var KNOWN_ADDONS = { backup: NOOP, restore: setupOauth, clear: NOOP, - status: null, - restart: null }, postgresql: { setup: setupPostgreSql, @@ -164,8 +143,6 @@ var KNOWN_ADDONS = { backup: backupPostgreSql, restore: restorePostgreSql, clear: clearPostgreSql, - status: statusContainerAddon.bind(null, 'postgresql', 'CLOUDRON_POSTGRESQL_TOKEN'), - restart: restartContainerAddon.bind(null, 'postgresql') }, recvmail: { setup: setupRecvMail, @@ -173,8 +150,6 @@ var KNOWN_ADDONS = { backup: NOOP, restore: setupRecvMail, clear: NOOP, - status: null, - restart: null }, redis: { setup: setupRedis, @@ -182,8 +157,6 @@ var KNOWN_ADDONS = { backup: backupRedis, restore: restoreRedis, clear: clearRedis, - status: null, - restart: null }, sendmail: { setup: setupSendMail, @@ -191,8 +164,6 @@ var KNOWN_ADDONS = { backup: NOOP, restore: setupSendMail, clear: NOOP, - status: null, - restart: null }, scheduler: { setup: NOOP, @@ -200,8 +171,6 @@ var KNOWN_ADDONS = { backup: NOOP, restore: NOOP, clear: NOOP, - status: null, - restart: null }, docker: { setup: NOOP, @@ -209,8 +178,34 @@ var KNOWN_ADDONS = { backup: NOOP, restore: NOOP, clear: NOOP, + } +}; + +const KNOWN_SERVICES = { + mail: { + status: containerStatus.bind(null, 'mail', 'CLOUDRON_MAIL_TOKEN'), + restart: restartContainer.bind(null, 'mail'), + defaultMemoryLimit: Math.max((1 + Math.round(os.totalmem()/(1024*1024*1024)/4)) * 128, 256) * 1024 * 1024 + }, + mongodb: { + status: containerStatus.bind(null, 'mongodb', 'CLOUDRON_MONGODB_TOKEN'), + restart: restartContainer.bind(null, 'mongodb'), + defaultMemoryLimit: (1 + Math.round(os.totalmem()/(1024*1024*1024)/4)) * 200 * 1024 * 1024 + }, + mysql: { + status: containerStatus.bind(null, 'mysql', 'CLOUDRON_MYSQL_TOKEN'), + restart: restartContainer.bind(null, 'mysql'), + defaultMemoryLimit: (1 + Math.round(os.totalmem()/(1024*1024*1024)/4)) * 256 * 1024 * 1024 + }, + postgresql: { + status: containerStatus.bind(null, 'postgresql', 'CLOUDRON_POSTGRESQL_TOKEN'), + restart: restartContainer.bind(null, 'postgresql'), + defaultMemoryLimit: (1 + Math.round(os.totalmem()/(1024*1024*1024)/4)) * 256 * 1024 * 1024 + }, + docker: { status: statusDocker, - restart: restartDocker + restart: restartDocker, + defaultMemoryLimit: 0 } }; @@ -244,21 +239,16 @@ function dumpPath(addon, appId) { } } -function restartContainerAddon(addonName, callback) { - assert.strictEqual(typeof addonName, 'string'); +function restartContainer(serviceName, callback) { + assert.strictEqual(typeof serviceName, 'string'); assert.strictEqual(typeof callback, 'function'); - // only allow certain addon types to be started - const allowedAddons = ['email', 'mysql', 'mongodb', 'postgresql']; - if (allowedAddons.indexOf(addonName) === -1) return callback(new AddonsError(AddonsError.NOT_SUPPORTED)); + assert(KNOWN_SERVICES[serviceName], `Unknown service ${serviceName}`); - // email container has a different name - const containerName = addonName === 'email' ? 'mail' : addonName; - - docker.stopContainer(containerName, function (error) { + docker.stopContainer(serviceName, function (error) { if (error) return callback(new AddonsError(AddonsError.INTERNAL_ERROR, error)); - docker.startContainer(containerName, function (error) { + docker.startContainer(serviceName, function (error) { if (error) return callback(new AddonsError(AddonsError.INTERNAL_ERROR, error)); callback(null); @@ -266,7 +256,7 @@ function restartContainerAddon(addonName, callback) { }); } -function statusContainerAddon(addonName, addonTokenName, callback) { +function containerStatus(addonName, addonTokenName, callback) { assert.strictEqual(typeof addonName, 'string'); assert.strictEqual(typeof addonTokenName, 'string'); assert.strictEqual(typeof callback, 'function'); @@ -294,23 +284,22 @@ function statusContainerAddon(addonName, addonTokenName, callback) { }); } -function getAddons(callback) { +function getServices(callback) { assert.strictEqual(typeof callback, 'function'); - // we currently list only addons which have a status function to report - var addons = Object.keys(KNOWN_ADDONS).filter(function (a) { return !!KNOWN_ADDONS[a].status; }); + let services = Object.keys(KNOWN_SERVICES); - callback(null, addons); + callback(null, services); } -function getAddon(addonName, callback) { - assert.strictEqual(typeof addonName, 'string'); +function getService(serviceName, callback) { + assert.strictEqual(typeof serviceName, 'string'); assert.strictEqual(typeof callback, 'function'); - if (!KNOWN_ADDONS[addonName]) return callback(new AddonsError(AddonsError.NOT_FOUND)); + if (!KNOWN_SERVICES[serviceName]) return callback(new AddonsError(AddonsError.NOT_FOUND)); var tmp = { - name: addonName, + name: serviceName, status: null, config: { // If a property is not set then we cannot change it through the api, see below @@ -322,18 +311,15 @@ function getAddon(addonName, callback) { settings.getPlatformConfig(function (error, platformConfig) { if (error) return callback(new AddonsError(AddonsError.INTERNAL_ERROR, error)); - if (platformConfig[addonName] && platformConfig[addonName].memory && platformConfig[addonName].memorySwap) { - tmp.config.memory = platformConfig[addonName].memory; - tmp.config.memorySwap = platformConfig[addonName].memorySwap; - } else if (DEFAULT_MEMORY_LIMITS[addonName]) { - tmp.config.memory = DEFAULT_MEMORY_LIMITS[addonName]; + if (platformConfig[serviceName] && platformConfig[serviceName].memory && platformConfig[serviceName].memorySwap) { + tmp.config.memory = platformConfig[serviceName].memory; + tmp.config.memorySwap = platformConfig[serviceName].memorySwap; + } else if (KNOWN_SERVICES[serviceName].defaultMemoryLimit) { + tmp.config.memory = KNOWN_SERVICES[serviceName].defaultMemoryLimit; tmp.config.memorySwap = tmp.config.memory * 2; } - // we are done if the addon has no specific status function - if (!KNOWN_ADDONS[addonName].status) return callback(null, tmp); - - KNOWN_ADDONS[addonName].status(function (error, result) { + KNOWN_SERVICES[serviceName].status(function (error, result) { if (error) return callback(new AddonsError(AddonsError.INTERNAL_ERROR, error)); tmp.status = result.status; @@ -346,24 +332,24 @@ function getAddon(addonName, callback) { }); } -function configureAddon(addonName, data, callback) { - assert.strictEqual(typeof addonName, 'string'); +function configureService(serviceName, data, callback) { + assert.strictEqual(typeof serviceName, 'string'); assert.strictEqual(typeof data, 'object'); assert.strictEqual(typeof callback, 'function'); - if (!KNOWN_ADDONS[addonName]) return callback(new AddonsError(AddonsError.NOT_FOUND)); + if (!KNOWN_SERVICES[serviceName]) return callback(new AddonsError(AddonsError.NOT_FOUND)); settings.getPlatformConfig(function (error, platformConfig) { if (error) return callback(new AddonsError(AddonsError.INTERNAL_ERROR, error)); - if (!platformConfig[addonName]) platformConfig[addonName] = {}; + if (!platformConfig[serviceName]) platformConfig[serviceName] = {}; // if not specified we clear the entry and use defaults if (!data.memory || !data.memorySwap) { - delete platformConfig[addonName]; + delete platformConfig[serviceName]; } else { - platformConfig[addonName].memory = data.memory; - platformConfig[addonName].memorySwap = data.memorySwap; + platformConfig[serviceName].memory = data.memory; + platformConfig[serviceName].memorySwap = data.memorySwap; } settings.setPlatformConfig(platformConfig, function (error) { @@ -424,14 +410,13 @@ function getLogs(addonName, options, callback) { callback(null, transformStream); } -function restartAddon(addon, callback) { - assert.strictEqual(typeof addon, 'string'); +function restartService(serviceName, callback) { + assert.strictEqual(typeof serviceName, 'string'); assert.strictEqual(typeof callback, 'function'); - if (!KNOWN_ADDONS[addon]) return callback(new AddonsError(AddonsError.NOT_FOUND)); - if (!KNOWN_ADDONS[addon].restart) return callback(null); + if (!KNOWN_SERVICES[serviceName]) return callback(new AddonsError(AddonsError.NOT_FOUND)); - KNOWN_ADDONS[addon].restart(callback); + KNOWN_ADDONS[serviceName].restart(callback); } function getAddonDetails(containerName, tokenEnvName, callback) { @@ -613,19 +598,19 @@ function updateAddonConfig(platformConfig, callback) { debug('updateAddonConfig: %j', platformConfig); // TODO: this should possibly also rollback memory to default - async.eachSeries([ 'mysql', 'postgresql', 'mail', 'mongodb' ], function iterator(containerName, iteratorCallback) { - const containerConfig = platformConfig[containerName]; + async.eachSeries([ 'mysql', 'postgresql', 'mail', 'mongodb' ], function iterator(serviceName, iteratorCallback) { + const containerConfig = platformConfig[serviceName]; let memory, memorySwap; if (containerConfig && containerConfig.memory && containerConfig.memorySwap) { memory = containerConfig.memory; memorySwap = containerConfig.memorySwap; } else { - memory = DEFAULT_MEMORY_LIMITS[containerName]; + memory = KNOWN_SERVICES[serviceName].defaultMemoryLimit; memorySwap = memory * 2; } - const args = `update --memory ${memory} --memory-swap ${memorySwap} ${containerName}`.split(' '); - shell.spawn(`update${containerName}`, '/usr/bin/docker', args, { }, iteratorCallback); + const args = `update --memory ${memory} --memory-swap ${memorySwap} ${serviceName}`.split(' '); + shell.spawn(`update${serviceName}`, '/usr/bin/docker', args, { }, iteratorCallback); }, callback); } diff --git a/src/routes/addons.js b/src/routes/addons.js index eb3bf2f97..5eaad5937 100644 --- a/src/routes/addons.js +++ b/src/routes/addons.js @@ -17,21 +17,21 @@ var addons = require('../addons.js'), HttpSuccess = require('connect-lastmile').HttpSuccess; function getAll(req, res, next) { - addons.getAddons(function (error, result) { + addons.getServices(function (error, result) { if (error) return next(new HttpError(500, error)); - next(new HttpSuccess(200, { addons: result })); + next(new HttpSuccess(200, { services: result })); }); } function get(req, res, next) { assert.strictEqual(typeof req.params.service, 'string'); - addons.getAddon(req.params.service, function (error, result) { - if (error && error.reason === AddonsError.NOT_FOUND) return next(new HttpError(404, 'No such addon')); + addons.getService(req.params.service, function (error, result) { + if (error && error.reason === AddonsError.NOT_FOUND) return next(new HttpError(404, 'No such service')); if (error) return next(new HttpError(500, error)); - next(new HttpSuccess(200, { addon: result })); + next(new HttpSuccess(200, { service: result })); }); } @@ -45,8 +45,8 @@ function configure(req, res, next) { memorySwap: req.body.memory * 2 }; - addons.configureAddon(req.params.service, data, function (error) { - if (error && error.reason === AddonsError.NOT_FOUND) return next(new HttpError(404, 'No such addon')); + addons.configureService(req.params.service, data, function (error) { + if (error && error.reason === AddonsError.NOT_FOUND) return next(new HttpError(404, 'No such service')); if (error) return next(new HttpError(500, error)); next(new HttpSuccess(202, {})); @@ -59,7 +59,7 @@ function getLogs(req, res, next) { var lines = req.query.lines ? parseInt(req.query.lines, 10) : 100; if (isNaN(lines)) return next(new HttpError(400, 'lines must be a number')); - debug(`Getting logs of addon ${req.params.service}`); + debug(`Getting logs of service ${req.params.service}`); var options = { lines: lines, @@ -68,7 +68,7 @@ function getLogs(req, res, next) { }; addons.getLogs(req.params.service, options, function (error, logStream) { - if (error && error.reason === AddonsError.NOT_FOUND) return next(new HttpError(404, 'No such addon')); + if (error && error.reason === AddonsError.NOT_FOUND) return next(new HttpError(404, 'No such service')); if (error) return next(new HttpError(500, error)); res.writeHead(200, { @@ -85,7 +85,7 @@ function getLogs(req, res, next) { function getLogStream(req, res, next) { assert.strictEqual(typeof req.params.service, 'string'); - debug(`Getting logstream of addon ${req.params.service}`); + debug(`Getting logstream of service ${req.params.service}`); var lines = req.query.lines ? parseInt(req.query.lines, 10) : -10; // we ignore last-event-id if (isNaN(lines)) return next(new HttpError(400, 'lines must be a valid number')); @@ -100,7 +100,7 @@ function getLogStream(req, res, next) { }; addons.getLogs(req.params.service, options, function (error, logStream) { - if (error && error.reason === AddonsError.NOT_FOUND) return next(new HttpError(404, 'No such addon')); + if (error && error.reason === AddonsError.NOT_FOUND) return next(new HttpError(404, 'No such service')); if (error) return next(new HttpError(500, error)); res.writeHead(200, { @@ -124,9 +124,9 @@ function getLogStream(req, res, next) { function restart(req, res, next) { assert.strictEqual(typeof req.params.service, 'string'); - debug(`Restarting addon ${req.params.service}`); + debug(`Restarting service ${req.params.service}`); - addons.restartAddon(req.params.service, function (error) { + addons.restartService(req.params.service, function (error) { if (error) return next(new HttpError(500, error)); next(new HttpSuccess(202, {}));