Add restart route for atomicity

This commit is contained in:
Girish Ramakrishnan
2019-12-20 10:29:29 -08:00
parent 887cbb0b22
commit 2692f6ef4e
6 changed files with 109 additions and 1 deletions

View File

@@ -43,6 +43,7 @@ exports = module.exports = {
start: start,
stop: stop,
restart: restart,
exec: exec,
@@ -79,6 +80,7 @@ exports = module.exports = {
ISTATE_PENDING_BACKUP: 'pending_backup', // backup the app. this is state because it blocks other operations
ISTATE_PENDING_START: 'pending_start',
ISTATE_PENDING_STOP: 'pending_stop',
ISTATE_PENDING_RESTART: 'pending_restart',
ISTATE_ERROR: 'error', // error executing last pending_* command
ISTATE_INSTALLED: 'installed', // app is installed
@@ -1678,6 +1680,30 @@ function stop(appId, callback) {
});
}
function restart(appId, callback) {
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof callback, 'function');
debug('Will restart app with id:%s', appId);
get(appId, function (error, app) {
if (error) return callback(error);
error = checkAppState(app, exports.ISTATE_PENDING_RESTART);
if (error) return callback(error);
const task = {
args: {},
values: { runState: exports.RSTATE_RUNNING }
};
addTask(appId, exports.ISTATE_PENDING_RESTART, task, function (error, result) {
if (error) return callback(error);
callback(null, { taskId: result.taskId });
});
});
}
function checkManifestConstraints(manifest) {
assert(manifest && typeof manifest === 'object');

View File

@@ -952,6 +952,27 @@ function stop(app, args, progressCallback, callback) {
});
}
function restart(app, args, progressCallback, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof args, 'object');
assert.strictEqual(typeof progressCallback, 'function');
assert.strictEqual(typeof callback, 'function');
async.series([
progressCallback.bind(null, { percent: 20, message: 'Restarting container' }),
docker.restartContainer.bind(null, app.id),
progressCallback.bind(null, { percent: 100, message: 'Done' }),
updateApp.bind(null, app, { installationState: apps.ISTATE_INSTALLED, error: null, health: null })
], function seriesDone(error) {
if (error) {
debugApp(app, 'error starting app: %s', error);
return updateApp(app, { installationState: apps.ISTATE_ERROR, error: makeTaskError(error, app) }, callback.bind(null, error));
}
callback(null);
});
}
function uninstall(app, args, progressCallback, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof args, 'object');
@@ -1029,6 +1050,8 @@ function run(appId, args, progressCallback, callback) {
return start(app, args, progressCallback, callback);
case apps.ISTATE_PENDING_STOP:
return stop(app, args, progressCallback, callback);
case apps.ISTATE_PENDING_RESTART:
return restart(app, args, progressCallback, callback);
default:
debugApp(app, 'apptask launched with invalid command');
return callback(new BoxError(BoxError.INTERNAL_ERROR, 'Unknown install command in apptask:' + app.installationState));

View File

@@ -14,6 +14,7 @@ exports = module.exports = {
downloadImage: downloadImage,
createContainer: createContainer,
startContainer: startContainer,
restartContainer: restartContainer,
stopContainer: stopContainer,
stopContainerByName: stopContainer,
stopContainers: stopContainers,
@@ -344,7 +345,23 @@ function startContainer(containerId, callback) {
container.start(function (error) {
if (error && error.statusCode === 404) return callback(new BoxError(BoxError.NOT_FOUND));
if (error && error.statusCode === 400) return callback(new BoxError(BoxError.BAD_FIELD, error)); // e.g start.sh is not executable
if (error && error.statusCode !== 304) return callback(new BoxError(BoxError.DOCKER_ERROR, error));
if (error && error.statusCode !== 304) return callback(new BoxError(BoxError.DOCKER_ERROR, error)); // 304 means already started
return callback(null);
});
}
function restartContainer(containerId, callback) {
assert.strictEqual(typeof containerId, 'string');
assert.strictEqual(typeof callback, 'function');
var container = gConnection.getContainer(containerId);
debug('Restarting container %s', containerId);
container.restart(function (error) {
if (error && error.statusCode === 404) return callback(new BoxError(BoxError.NOT_FOUND));
if (error && error.statusCode === 400) return callback(new BoxError(BoxError.BAD_FIELD, error)); // e.g start.sh is not executable
if (error && error.statusCode !== 204) return callback(new BoxError(BoxError.DOCKER_ERROR, error));
return callback(null);
});

View File

@@ -32,6 +32,7 @@ exports = module.exports = {
stopApp: stopApp,
startApp: startApp,
restartApp: restartApp,
exec: exec,
execWebSocket: execWebSocket,
@@ -480,6 +481,18 @@ function stopApp(req, res, next) {
});
}
function restartApp(req, res, next) {
assert.strictEqual(typeof req.params.id, 'string');
debug('Restart app id:%s', req.params.id);
apps.restart(req.params.id, function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
});
}
function updateApp(req, res, next) {
assert.strictEqual(typeof req.params.id, 'string');
assert.strictEqual(typeof req.body, 'object');

View File

@@ -1570,6 +1570,34 @@ describe('App API', function () {
});
});
});
it('can restart app', function (done) {
superagent.post(SERVER_URL + '/api/v1/apps/' + APP_ID + '/restart')
.query({ access_token: token })
.end(function (err, res) {
expect(res.statusCode).to.equal(202);
taskId = res.body.taskId;
done();
});
});
it('wait for app to restart', function (done) {
waitForTask(taskId, function () { setTimeout(done, 12000); }); // give app 12 seconds (to die and start)
});
it('did restart the app', function (done) {
apps.get(APP_ID, function (error, app) {
if (error) return done(error);
superagent.get('http://localhost:' + app.httpPort + APP_MANIFEST.healthCheckPath)
.end(function (err, res) {
if (res && res.statusCode === 200) return done();
done(new Error('app is not running'));
});
});
});
});
describe('uninstall', function () {

View File

@@ -277,6 +277,7 @@ function initializeExpressSync() {
router.get ('/api/v1/apps/:id/backups', appsManageScope, routes.apps.listBackups);
router.post('/api/v1/apps/:id/stop', appsManageScope, routes.apps.stopApp);
router.post('/api/v1/apps/:id/start', appsManageScope, routes.apps.startApp);
router.post('/api/v1/apps/:id/restart', appsManageScope, routes.apps.restartApp);
router.get ('/api/v1/apps/:id/logstream', appsManageScope, routes.apps.getLogStream);
router.get ('/api/v1/apps/:id/logs', appsManageScope, routes.apps.getLogs);
router.get ('/api/v1/apps/:id/exec', appsManageScope, routes.apps.exec);