diff --git a/src/caas.js b/src/caas.js index 6b8cd5871..dfec1ae67 100644 --- a/src/caas.js +++ b/src/caas.js @@ -4,28 +4,18 @@ exports = module.exports = { verifySetupToken: verifySetupToken, setupDone: setupDone, - changePlan: changePlan, 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'), settings = require('./settings.js'), - shell = require('./shell.js'), superagent = require('superagent'), - tasks = require('./tasks.js'), - util = require('util'), - _ = require('underscore'); - -const RETIRE_CMD = path.join(__dirname, 'scripts/retire.sh'); + util = require('util'); function CaasError(reason, errorOrMessage) { assert.strictEqual(typeof reason, 'string'); @@ -52,20 +42,6 @@ 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'); - 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'); @@ -116,60 +92,6 @@ function setupDone(setupToken, callback) { }); }); } -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); - tasks.update(tasks.TASK_MIGRATE, { percent: -1, errorMessage: `Backup failed: ${error.message}` }, NOOP_CALLBACK); - } - - tasks.update(tasks.TASK_MIGRATE, { percent: 10, message: 'Backing up for migration' }, NOOP_CALLBACK); - - // initiate the migration in the background - backups.backupBoxAndApps((progress) => tasks.update(tasks.TASK_MIGRATE, { percent: 10+progress.percent*30/100, message: progress.message }, NOOP_CALLBACK), 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))); - - tasks.update(tasks.TASK_MIGRATE, { percent: 40, message: 'Migrating' }, NOOP_CALLBACK); - - 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); - }); -} function sendHeartbeat() { assert(config.provider() === 'caas', 'Heartbeat is only sent for managed cloudrons'); @@ -186,27 +108,6 @@ function sendHeartbeat() { }); } -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'); diff --git a/src/locker.js b/src/locker.js index ac679714f..c40db92ed 100644 --- a/src/locker.js +++ b/src/locker.js @@ -18,7 +18,6 @@ Locker.prototype.OP_BOX_UPDATE = 'box_update'; Locker.prototype.OP_PLATFORM_START = 'platform_start'; Locker.prototype.OP_FULL_BACKUP = 'full_backup'; Locker.prototype.OP_APPTASK = 'apptask'; -Locker.prototype.OP_MIGRATE = 'migrate'; Locker.prototype.lock = function (operation) { assert.strictEqual(typeof operation, 'string'); diff --git a/src/routes/caas.js b/src/routes/caas.js deleted file mode 100644 index 7cac8d5a3..000000000 --- a/src/routes/caas.js +++ /dev/null @@ -1,60 +0,0 @@ -'use strict'; - -exports = module.exports = { - getConfig: getConfig, - changePlan: changePlan -}; - -var caas = require('../caas.js'), - CaasError = require('../caas.js').CaasError, - config = require('../config.js'), - debug = require('debug')('box:routes/cloudron'), - HttpError = require('connect-lastmile').HttpError, - HttpSuccess = require('connect-lastmile').HttpSuccess, - _ = require('underscore'); - -function getConfig(req, res, next) { - if (config.provider() !== 'caas') return next(new HttpError(422, 'Cannot use this API with this provider')); - - caas.getBoxAndUserDetails(function (error, result) { - if (error) return next(new HttpError(500, error)); - - // the result is { box: { region, size, plan }, user: { billing, currency } } - next(new HttpSuccess(200, { - region: result.box.region, - size: result.box.size, - billing: !!result.user.billing, - plan: result.box.plan, - currency: result.user.currency - })); - }); -} - -function changePlan(req, res, next) { - if (config.provider() !== 'caas') return next(new HttpError(422, 'Cannot use this API with this provider')); - - if ('size' in req.body && typeof req.body.size !== 'string') return next(new HttpError(400, 'size must be string')); - if ('region' in req.body && typeof req.body.region !== 'string') return next(new HttpError(400, 'region must be string')); - - if ('domain' in req.body) { - if (typeof req.body.domain !== 'string') return next(new HttpError(400, 'domain must be string')); - if (typeof req.body.provider !== 'string') return next(new HttpError(400, 'provider must be string')); - } - - if ('zoneName' in req.body && typeof req.body.zoneName !== 'string') return next(new HttpError(400, 'zoneName must be string')); - - debug('Migration requested domain:%s size:%s region:%s', req.body.domain, req.body.size, req.body.region); - - var options = _.pick(req.body, 'domain', 'size', 'region'); - if (Object.keys(options).length === 0) return next(new HttpError(400, 'no migrate option provided')); - - if (options.domain) options.domain = options.domain.toLowerCase(); - - caas.changePlan(req.body, function (error) { // pass req.body because 'domain' can have arbitrary options - if (error && error.reason === CaasError.BAD_STATE) return next(new HttpError(409, error.message)); - if (error && error.reason === CaasError.BAD_FIELD) return next(new HttpError(400, error.message)); - if (error) return next(new HttpError(500, error)); - - next(new HttpSuccess(202, {})); - }); -} diff --git a/src/routes/index.js b/src/routes/index.js index a0358feed..d17168859 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -4,7 +4,6 @@ exports = module.exports = { accesscontrol: require('./accesscontrol.js'), apps: require('./apps.js'), backups: require('./backups.js'), - caas: require('./caas.js'), clients: require('./clients.js'), cloudron: require('./cloudron.js'), developer: require('./developer.js'), diff --git a/src/routes/test/caas-test.js b/src/routes/test/caas-test.js index 3a39879c7..0b4031a4e 100644 --- a/src/routes/test/caas-test.js +++ b/src/routes/test/caas-test.js @@ -145,29 +145,6 @@ describe('Caas', function () { }); }); - describe('get config', function () { - before(setup); - after(cleanup); - - it('succeeds (admin)', function (done) { - var scope = nock(config.apiServerOrigin()) - .get('/api/v1/boxes/BOX_ID?token=ACCESS_TOKEN2') - .reply(200, { box: { region: 'sfo', size: '1gb' }, user: { }}); - - superagent.get(SERVER_URL + '/api/v1/caas/config') - .query({ access_token: token }) - .end(function (error, result) { - expect(result.statusCode).to.equal(200); - expect(result.body.size).to.eql('1gb'); - expect(result.body.region).to.eql('sfo'); - - expect(scope.isDone()).to.be.ok(); - - done(); - }); - }); - }); - describe('Backups API', function () { var scope1 = nock(config.apiServerOrigin()).post('/api/v1/boxes/BOX_ID/awscredentials?token=BACKUP_TOKEN') .reply(201, { credentials: { AccessKeyId: 'accessKeyId', SecretAccessKey: 'secretAccessKey' } }, { 'Content-Type': 'application/json' }); @@ -191,169 +168,5 @@ describe('Caas', function () { }); }); }); - - xdescribe('migrate', function () { - before(function (done) { - async.series([ - setup, - - function (callback) { - var scope1 = nock(config.apiServerOrigin()).get('/api/v1/boxes/BOX_ID/setup/verify?setupToken=somesetuptoken').reply(200, {}); - var scope2 = nock(config.apiServerOrigin()).post('/api/v1/boxes/BOX_ID/setup/done?setupToken=somesetuptoken').reply(201, {}); - - superagent.post(SERVER_URL + '/api/v1/cloudron/activate') - .query({ setupToken: 'somesetuptoken' }) - .send({ username: USERNAME, password: PASSWORD, email: EMAIL }) - .end(function (error, result) { - expect(result).to.be.ok(); - expect(scope1.isDone()).to.be.ok(); - expect(scope2.isDone()).to.be.ok(); - - // stash token for further use - token = result.body.token; - - callback(); - }); - } - ], done); - }); - - after(function (done) { - locker.unlock(locker._operation); // migrate never unlocks - cleanup(done); - }); - - it('fails without token', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/migrate') - .send({ size: 'small', region: 'sfo'}) - .end(function (error, result) { - expect(result.statusCode).to.equal(401); - done(); - }); - }); - - it('fails without password', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/migrate') - .send({ size: 'small', region: 'sfo'}) - .query({ access_token: token }) - .end(function (error, result) { - expect(result.statusCode).to.equal(400); - done(); - }); - }); - - it('succeeds without size', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/migrate') - .send({ region: 'sfo', password: PASSWORD }) - .query({ access_token: token }) - .end(function (error, result) { - expect(result.statusCode).to.equal(202); - done(); - }); - }); - - it('fails with wrong size type', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/migrate') - .send({ size: 4, region: 'sfo', password: PASSWORD }) - .query({ access_token: token }) - .end(function (error, result) { - expect(result.statusCode).to.equal(400); - done(); - }); - }); - - it('succeeds without region', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/migrate') - .send({ size: 'small', password: PASSWORD }) - .query({ access_token: token }) - .end(function (error, result) { - expect(result.statusCode).to.equal(202); - done(); - }); - }); - - it('fails with wrong region type', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/migrate') - .send({ size: 'small', region: 4, password: PASSWORD }) - .query({ access_token: token }) - .end(function (error, result) { - expect(result.statusCode).to.equal(400); - done(); - }); - }); - - it('fails when in wrong state', function (done) { - var scope2 = nock(config.apiServerOrigin()) - .post('/api/v1/boxes/BOX_ID/awscredentials?token=BACKUP_TOKEN') - .reply(201, { credentials: { AccessKeyId: 'accessKeyId', SecretAccessKey: 'secretAccessKey', SessionToken: 'sessionToken' } }); - - var scope3 = nock(config.apiServerOrigin()) - .post('/api/v1/boxes/BOX_ID/backupDone?token=APPSTORE_TOKEN', function (body) { - return body.boxVersion && body.restoreKey && !body.appId && !body.appVersion && body.appBackupIds.length === 0; - }) - .reply(200, { id: 'someid' }); - - var scope1 = nock(config.apiServerOrigin()) - .post('/api/v1/boxes/BOX_ID/migrate?token=APPSTORE_TOKEN', function (body) { - return body.size && body.region && body.restoreKey; - }).reply(409, {}); - - injectShellMock(); - - superagent.post(SERVER_URL + '/api/v1/cloudron/migrate') - .send({ size: 'small', region: 'sfo', password: PASSWORD }) - .query({ access_token: token }) - .end(function (error, result) { - expect(result.statusCode).to.equal(202); - - function checkAppstoreServerCalled() { - if (scope1.isDone() && scope2.isDone() && scope3.isDone()) { - restoreShellMock(); - return done(); - } - - setTimeout(checkAppstoreServerCalled, 100); - } - - checkAppstoreServerCalled(); - }); - }); - - it('succeeds', function (done) { - var scope1 = nock(config.apiServerOrigin()).post('/api/v1/boxes/BOX_ID/migrate?token=APPSTORE_TOKEN', function (body) { - return body.size && body.region && body.restoreKey; - }).reply(202, {}); - - var scope2 = nock(config.apiServerOrigin()) - .post('/api/v1/boxes/BOX_ID/backupDone?token=APPSTORE_TOKEN', function (body) { - return body.boxVersion && body.restoreKey && !body.appId && !body.appVersion && body.appBackupIds.length === 0; - }) - .reply(200, { id: 'someid' }); - - var scope3 = nock(config.apiServerOrigin()) - .post('/api/v1/boxes/BOX_ID/awscredentials?token=BACKUP_TOKEN') - .reply(201, { credentials: { AccessKeyId: 'accessKeyId', SecretAccessKey: 'secretAccessKey', SessionToken: 'sessionToken' } }); - - injectShellMock(); - - superagent.post(SERVER_URL + '/api/v1/cloudron/migrate') - .send({ size: 'small', region: 'sfo', password: PASSWORD }) - .query({ access_token: token }) - .end(function (error, result) { - expect(result.statusCode).to.equal(202); - - function checkAppstoreServerCalled() { - if (scope1.isDone() && scope2.isDone() && scope3.isDone()) { - restoreShellMock(); - return done(); - } - - setTimeout(checkAppstoreServerCalled, 100); - } - - checkAppstoreServerCalled(); - }); - }); - }); }); diff --git a/src/server.js b/src/server.js index 3bfac1b76..919eefd19 100644 --- a/src/server.js +++ b/src/server.js @@ -297,10 +297,6 @@ function initializeExpressSync() { router.get ('/api/v1/services/:service/logstream', cloudronScope, routes.services.getLogStream); router.post('/api/v1/services/:service/restart', cloudronScope, routes.services.restart); - // caas routes - router.get('/api/v1/caas/config', cloudronScope, routes.caas.getConfig); - router.post('/api/v1/caas/change_plan', cloudronScope, routes.users.verifyPassword, routes.caas.changePlan); - // disable server socket "idle" timeout. we use the timeout middleware to handle timeouts on a route level // we rely on nginx for timeouts on the TCP level (see client_header_timeout) httpServer.setTimeout(0); diff --git a/src/tasks.js b/src/tasks.js index 476259e96..7bb19673b 100644 --- a/src/tasks.js +++ b/src/tasks.js @@ -17,9 +17,7 @@ exports = module.exports = { // task types. if you add a task here, fill up the function table in taskworker TASK_BACKUP: 'backup', TASK_UPDATE: 'update', - TASK_MIGRATE: 'migrate', TASK_RENEW_CERTS: 'renewcerts', - TASK_RESTORE: 'restore', // testing _TASK_IDENTITY: '_identity',