267 lines
10 KiB
JavaScript
267 lines
10 KiB
JavaScript
'use strict';
|
|
|
|
exports = module.exports = {
|
|
verifySetupToken: verifySetupToken,
|
|
setupDone: setupDone,
|
|
|
|
changePlan: changePlan,
|
|
upgrade: upgrade,
|
|
sendHeartbeat: sendHeartbeat,
|
|
getBoxAndUserDetails: getBoxAndUserDetails,
|
|
setPtrRecord: setPtrRecord,
|
|
|
|
CaasError: CaasError
|
|
};
|
|
|
|
var assert = require('assert'),
|
|
backups = require('./backups.js'),
|
|
config = require('./config.js'),
|
|
debug = require('debug')('box:caas'),
|
|
locker = require('./locker.js'),
|
|
path = require('path'),
|
|
progress = require('./progress.js'),
|
|
settings = require('./settings.js'),
|
|
shell = require('./shell.js'),
|
|
superagent = require('superagent'),
|
|
util = require('util'),
|
|
_ = require('underscore');
|
|
|
|
const RETIRE_CMD = path.join(__dirname, 'scripts/retire.sh');
|
|
|
|
function CaasError(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(CaasError, Error);
|
|
CaasError.BAD_FIELD = 'Field error';
|
|
CaasError.BAD_STATE = 'Bad state';
|
|
CaasError.INVALID_TOKEN = 'Invalid Token';
|
|
CaasError.INTERNAL_ERROR = 'Internal Error';
|
|
CaasError.EXTERNAL_ERROR = 'External Error';
|
|
|
|
var NOOP_CALLBACK = function (error) { if (error) debug(error); };
|
|
|
|
function retire(reason, info, callback) {
|
|
assert(reason === 'migrate' || reason === 'upgrade');
|
|
info = info || { };
|
|
callback = callback || NOOP_CALLBACK;
|
|
|
|
var data = {
|
|
apiServerOrigin: config.apiServerOrigin(),
|
|
adminFqdn: config.adminFqdn()
|
|
};
|
|
shell.sudo('retire', [ RETIRE_CMD, reason, JSON.stringify(info), JSON.stringify(data) ], callback);
|
|
}
|
|
|
|
function getCaasConfig(callback) {
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
settings.getCaasConfig(function (error, result) {
|
|
if (error) return callback(new CaasError(CaasError.INTERNAL_ERROR, error));
|
|
|
|
callback(null, result);
|
|
});
|
|
}
|
|
|
|
function verifySetupToken(setupToken, callback) {
|
|
assert.strictEqual(typeof setupToken, 'string');
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
settings.getCaasConfig(function (error, caasConfig) {
|
|
if (error) return callback(new CaasError(CaasError.INTERNAL_ERROR, error));
|
|
|
|
superagent.get(config.apiServerOrigin() + '/api/v1/boxes/' + caasConfig.boxId + '/setup/verify').query({ setupToken: setupToken })
|
|
.timeout(30 * 1000)
|
|
.end(function (error, result) {
|
|
if (error && !error.response) return callback(new CaasError(CaasError.EXTERNAL_ERROR, error));
|
|
if (result.statusCode === 403) return callback(new CaasError(CaasError.INVALID_TOKEN));
|
|
if (result.statusCode === 409) return callback(new CaasError(CaasError.BAD_STATE, 'Already setup'));
|
|
if (result.statusCode !== 200) return callback(new CaasError(CaasError.EXTERNAL_ERROR, error));
|
|
|
|
callback(null);
|
|
});
|
|
});
|
|
}
|
|
|
|
function setupDone(setupToken, callback) {
|
|
assert.strictEqual(typeof setupToken, 'string');
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
settings.getCaasConfig(function (error, caasConfig) {
|
|
if (error) return callback(new CaasError(CaasError.INTERNAL_ERROR, error));
|
|
|
|
// Now let the api server know we got activated
|
|
superagent.post(config.apiServerOrigin() + '/api/v1/boxes/' + caasConfig.boxId + '/setup/done').query({ setupToken: setupToken })
|
|
.timeout(30 * 1000)
|
|
.end(function (error, result) {
|
|
if (error && !error.response) return callback(new CaasError(CaasError.EXTERNAL_ERROR, error));
|
|
if (result.statusCode === 403) return callback(new CaasError(CaasError.INVALID_TOKEN));
|
|
if (result.statusCode === 409) return callback(new CaasError(CaasError.BAD_STATE, 'Already setup'));
|
|
if (result.statusCode !== 201) return callback(new CaasError(CaasError.EXTERNAL_ERROR, error));
|
|
|
|
callback(null);
|
|
});
|
|
});
|
|
}
|
|
function doMigrate(options, caasConfig, callback) {
|
|
assert.strictEqual(typeof options, 'object');
|
|
assert.strictEqual(typeof caasConfig, 'object');
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
var error = locker.lock(locker.OP_MIGRATE);
|
|
if (error) return callback(new CaasError(CaasError.BAD_STATE, error.message));
|
|
|
|
function unlock(error) {
|
|
debug('Failed to migrate', error);
|
|
locker.unlock(locker.OP_MIGRATE);
|
|
progress.set(progress.MIGRATE, -1, 'Backup failed: ' + error.message);
|
|
}
|
|
|
|
progress.set(progress.MIGRATE, 10, 'Backing up for migration');
|
|
|
|
// initiate the migration in the background
|
|
backups.backupBoxAndApps({ userId: null, username: 'migrator' }, function (error) {
|
|
if (error) return unlock(error);
|
|
|
|
debug('migrate: domain: %s size %s region %s', options.domain, options.size, options.region);
|
|
|
|
superagent
|
|
.post(config.apiServerOrigin() + '/api/v1/boxes/' + caasConfig.boxId + '/migrate')
|
|
.query({ token: caasConfig.token })
|
|
.send(options)
|
|
.timeout(30 * 1000)
|
|
.end(function (error, result) {
|
|
if (error && !error.response) return unlock(error); // network error
|
|
if (result.statusCode === 409) return unlock(new CaasError(CaasError.BAD_STATE));
|
|
if (result.statusCode === 404) return unlock(new CaasError(CaasError.NOT_FOUND));
|
|
if (result.statusCode !== 202) return unlock(new CaasError(CaasError.EXTERNAL_ERROR, util.format('%s %j', result.status, result.body)));
|
|
|
|
progress.set(progress.MIGRATE, 10, 'Migrating');
|
|
|
|
retire('migrate', _.pick(options, 'domain', 'size', 'region'));
|
|
});
|
|
});
|
|
|
|
callback(null);
|
|
}
|
|
|
|
function changePlan(options, callback) {
|
|
assert.strictEqual(typeof options, 'object');
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
if (config.isDemo()) return callback(new CaasError(CaasError.BAD_FIELD, 'Not allowed in demo mode'));
|
|
|
|
getCaasConfig(function (error, result) {
|
|
if (error) return callback(error);
|
|
|
|
doMigrate(options, result, callback);
|
|
});
|
|
}
|
|
|
|
// this function expects a lock
|
|
function upgrade(boxUpdateInfo, callback) {
|
|
assert(boxUpdateInfo !== null && typeof boxUpdateInfo === 'object');
|
|
|
|
function upgradeError(e) {
|
|
progress.set(progress.UPDATE, -1, e.message);
|
|
callback(e);
|
|
}
|
|
|
|
progress.set(progress.UPDATE, 5, 'Backing up for upgrade');
|
|
|
|
backups.backupBoxAndApps({ userId: null, username: 'upgrader' }, function (error) {
|
|
if (error) return upgradeError(error);
|
|
|
|
getCaasConfig(function (error, result) {
|
|
if (error) return upgradeError(error);
|
|
|
|
superagent.post(config.apiServerOrigin() + '/api/v1/boxes/' + result.boxId + '/upgrade')
|
|
.query({ token: result.token })
|
|
.send({ version: boxUpdateInfo.version })
|
|
.timeout(30 * 1000)
|
|
.end(function (error, result) {
|
|
if (error && !error.response) return upgradeError(new Error('Network error making upgrade request: ' + error));
|
|
if (result.statusCode !== 202) return upgradeError(new Error(util.format('Server not ready to upgrade. statusCode: %s body: %j', result.status, result.body)));
|
|
|
|
progress.set(progress.UPDATE, 10, 'Updating base system');
|
|
|
|
// no need to unlock since this is the last thing we ever do on this box
|
|
callback();
|
|
|
|
retire('upgrade');
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function sendHeartbeat() {
|
|
assert(config.provider() === 'caas', 'Heartbeat is only sent for managed cloudrons');
|
|
|
|
getCaasConfig(function (error, result) {
|
|
if (error) return debug('Caas config missing', error);
|
|
|
|
var url = config.apiServerOrigin() + '/api/v1/boxes/' + result.boxId + '/heartbeat';
|
|
superagent.post(url).query({ token: result.token, version: config.version() }).timeout(30 * 1000).end(function (error, result) {
|
|
if (error && !error.response) debug('Network error sending heartbeat.', error);
|
|
else if (result.statusCode !== 200) debug('Server responded to heartbeat with %s %s', result.statusCode, result.text);
|
|
else debug('Heartbeat sent to %s', url);
|
|
});
|
|
});
|
|
}
|
|
|
|
function getBoxAndUserDetails(callback) {
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
if (config.provider() !== 'caas') return callback(null, {});
|
|
|
|
getCaasConfig(function (error, caasConfig) {
|
|
if (error) return callback(error);
|
|
|
|
superagent
|
|
.get(config.apiServerOrigin() + '/api/v1/boxes/' + caasConfig.boxId)
|
|
.query({ token: caasConfig.token })
|
|
.timeout(30 * 1000)
|
|
.end(function (error, result) {
|
|
if (error && !error.response) return callback(new CaasError(CaasError.EXTERNAL_ERROR, 'Cannot reach appstore'));
|
|
if (result.statusCode !== 200) return callback(new CaasError(CaasError.EXTERNAL_ERROR, util.format('%s %j', result.statusCode, result.body)));
|
|
|
|
return callback(null, result.body);
|
|
});
|
|
});
|
|
}
|
|
|
|
function setPtrRecord(domain, callback) {
|
|
assert.strictEqual(typeof domain, 'string');
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
getCaasConfig(function (error, result) {
|
|
if (error) return callback(error);
|
|
|
|
superagent
|
|
.post(config.apiServerOrigin() + '/api/v1/boxes/' + result.boxId + '/ptr')
|
|
.query({ token: result.token })
|
|
.send({ domain: domain })
|
|
.timeout(5 * 1000)
|
|
.end(function (error, result) {
|
|
if (error && !error.response) return callback(new CaasError(CaasError.EXTERNAL_ERROR, 'Cannot reach appstore'));
|
|
if (result.statusCode !== 202) return callback(new CaasError(CaasError.EXTERNAL_ERROR, util.format('%s %j', result.statusCode, result.body)));
|
|
|
|
return callback(null);
|
|
});
|
|
});
|
|
}
|