Fixup repair route

* Do not allow scheduling tasks in error state
* Only repair is allowed in error state
* Use the error object to track what to 'repair' (like the lastState)
* If uninstall failed, repair will do uninstall
* If move dir failed, repair will do move dir
This commit is contained in:
Girish Ramakrishnan
2019-09-21 19:45:55 -07:00
parent 37f28746fc
commit ff1f448860
5 changed files with 156 additions and 45 deletions

View File

@@ -646,8 +646,11 @@ function scheduleTask(appId, args, values, callback) {
debug(`scheduleTask: task ${taskId} of $${appId} completed`);
if (error && (error.code === tasks.ECRASHED || error.code === tasks.ESTOPPED)) { // if task crashed, update the error
debug(`Apptask crashed/stopped: ${error.message}`);
const crashed = error.code === tasks.ECRASHED, stopped = error.code === tasks.ESTOPPED;
appdb.update(appId, { installationState: exports.ISTATE_ERROR, error: { reason: BoxError.TASK_ERROR, crashed, stopped, message: error.message }, taskId: null }, NOOP_CALLBACK);
let boxError = new BoxError(BoxError.TASK_ERROR, error.message);
boxError.details.crashed = error.code === tasks.ECRASHED;
boxError.details.stopped = error.code === tasks.ESTOPPED;
boxError.details.task = { args, installationState: values.installationState }; // see also apptask makeTaskError
appdb.update(appId, { installationState: exports.ISTATE_ERROR, error: boxError.toPlainObject(), taskId: null }, NOOP_CALLBACK);
} else if (values.installationState !== exports.ISTATE_PENDING_UNINSTALL) { // clear out taskId since it's done
appdb.update(appId, { taskId: null }, NOOP_CALLBACK);
}
@@ -658,6 +661,21 @@ function scheduleTask(appId, args, values, callback) {
});
}
function checkAppState(app, state) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof state, 'string');
if (app.taskId) return new AppsError(AppsError.BAD_STATE, `Not allowed in this app state : ${app.installationState} / ${app.runState}`);
if (state === exports.ISTATE_PENDING_REPAIR) {
if (app.installationState !== exports.ISTATE_ERROR) return new AppsError(AppsError.BAD_STATE, 'Not allowed in error state');
} else {
if (app.installationState === exports.ISTATE_ERROR) return new AppsError(AppsError.BAD_STATE, 'Not allowed in error state');
}
return null;
}
function install(data, user, auditSource, callback) {
assert(data && typeof data === 'object');
assert(user && typeof user === 'object');
@@ -907,6 +925,9 @@ function setMemoryLimit(appId, memoryLimit, auditSource, callback) {
get(appId, function (error, app) {
if (error) return callback(error);
error = checkAppState(app, exports.ISTATE_PENDING_RESIZE);
if (error) return callback(error);
error = validateMemoryLimit(app.manifest, memoryLimit);
if (error) return callback(error);
@@ -929,6 +950,9 @@ function setEnvironment(appId, env, auditSource, callback) {
get(appId, function (error, app) {
if (error) return callback(error);
error = checkAppState(app, exports.ISTATE_PENDING_RECREATE_CONTAINER);
if (error) return callback(error);
error = validateEnv(env);
if (error) return callback(error);
@@ -951,6 +975,9 @@ function setDebugMode(appId, debugMode, auditSource, callback) {
get(appId, function (error, app) {
if (error) return callback(error);
error = checkAppState(app, exports.ISTATE_PENDING_DEBUG);
if (error) return callback(error);
error = validateDebugMode(debugMode);
if (error) return callback(error);
@@ -973,6 +1000,9 @@ function setMailbox(appId, mailboxName, auditSource, callback) {
get(appId, function (error, app) {
if (error) return callback(error);
error = checkAppState(app, exports.ISTATE_PENDING_RECREATE_CONTAINER);
if (error) return callback(error);
if (mailboxName) {
error = mail.validateName(mailboxName);
if (error) return callback(new AppsError(AppsError.BAD_FIELD, error.message, { field: 'mailboxName' }));
@@ -1095,6 +1125,9 @@ function setLocation(appId, data, auditSource, callback) {
get(appId, function (error, app) {
if (error) return callback(error);
error = checkAppState(app, exports.ISTATE_PENDING_LOCATION_CHANGE);
if (error) return callback(error);
let values = {
installationState: exports.ISTATE_PENDING_LOCATION_CHANGE,
// these are intentionally reset, if not set
@@ -1152,6 +1185,9 @@ function setDataDir(appId, dataDir, auditSource, callback) {
get(appId, function (error, app) {
if (error) return callback(error);
error = checkAppState(app, exports.ISTATE_PENDING_DATA_DIR_MIGRATION);
if (error) return callback(error);
error = validateDataDir(dataDir);
if (error) return callback(error);
@@ -1175,7 +1211,9 @@ function update(appId, data, auditSource, callback) {
get(appId, function (error, app) {
if (error) return callback(error);
if (app.taskId) return callback(new AppsError(AppsError.BAD_STATE, `Not allowed in this app state : ${app.installationState} / ${app.runState}`));
error = checkAppState(app, exports.ISTATE_PENDING_UPDATE);
if (error) return callback(error);
downloadManifest(data.appStoreId, data.manifest, function (error, appStoreId, manifest) {
if (error) return callback(error);
@@ -1305,12 +1343,25 @@ function repair(appId, data, auditSource, callback) {
debug('Will repair app with id:%s', appId);
scheduleTask(appId, { /* task args */ }, { installationState: exports.ISTATE_PENDING_REPAIR }, function (error, result) {
get(appId, function (error, app) {
if (error) return callback(error);
eventlog.add(eventlog.ACTION_APP_REPAIR, auditSource, { taskId: result.taskId });
error = checkAppState(app, exports.ISTATE_PENDING_REPAIR);
if (error) return callback(error);
callback(null, { taskId: result.taskId });
let values = _.pick(data, 'location', 'domain', 'alternateDomains'); // FIXME: validate
values.installationState = exports.ISTATE_PENDING_REPAIR;
const restoreConfig = data.backupId ? { backupId: data.backupId, backupFormat: data.backupFormat, oldManifest: app.manifest } : null; // when null, apptask simply reinstalls
const overwriteDns = 'overwriteDns' in data ? data.overwriteDns : false;
scheduleTask(appId, { restoreConfig, overwriteDns }, values, function (error, result) {
if (error) return callback(error);
eventlog.add(eventlog.ACTION_APP_REPAIR, auditSource, { taskId: result.taskId, app });
callback(null, { taskId: result.taskId });
});
});
}
@@ -1324,7 +1375,9 @@ function restore(appId, data, auditSource, callback) {
get(appId, function (error, app) {
if (error) return callback(error);
if (app.taskId) return callback(new AppsError(AppsError.BAD_STATE, `Not allowed in this app state : ${app.installationState} / ${app.runState}`));
error = checkAppState(app, exports.ISTATE_PENDING_RESTORE);
if (error) return callback(error);
// for empty or null backupId, use existing manifest to mimic a reinstall
var func = data.backupId ? backups.get.bind(null, data.backupId) : function (next) { return next(null, { manifest: app.manifest }); };
@@ -1480,7 +1533,9 @@ function uninstall(appId, auditSource, callback) {
get(appId, function (error, app) {
if (error) return callback(error);
if (app.taskId) return callback(new AppsError(AppsError.BAD_STATE, `Not allowed in this app state : ${app.installationState} / ${app.runState}`));
error = checkAppState(app, exports.ISTATE_PENDING_UNINSTALL);
if (error) return callback(error);
appstore.unpurchaseApp(appId, { appstoreId: app.appStoreId, manifestId: app.manifest.id }, function (error) {
if (error && error.reason === AppstoreError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND));
@@ -1508,7 +1563,9 @@ function start(appId, callback) {
get(appId, function (error, app) {
if (error) return callback(error);
if (app.taskId) return callback(new AppsError(AppsError.BAD_STATE, `Not allowed in this app state : ${app.installationState} / ${app.runState}`));
error = checkAppState(app, exports.exports.ISTATE_INSTALLED); // FIXME
if (error) return callback(error);
scheduleTask(appId, { /* args */ }, { runState: exports.RSTATE_PENDING_START }, callback);
});
@@ -1522,7 +1579,9 @@ function stop(appId, callback) {
get(appId, function (error, app) {
if (error) return callback(error);
if (app.taskId) return callback(new AppsError(AppsError.BAD_STATE, `Not allowed in this app state : ${app.installationState} / ${app.runState}`));
error = checkAppState(app, exports.ISTATE_INSTALLED); // FIXME
if (error) return callback(error);
scheduleTask(appId, { /* args */ }, { runState: exports.RSTATE_PENDING_STOP }, callback);
});
@@ -1666,7 +1725,9 @@ function backup(appId, callback) {
get(appId, function (error, app) {
if (error) return callback(error);
if (app.taskId) return callback(new AppsError(AppsError.BAD_STATE, `Not allowed in this app state : ${app.installationState} / ${app.runState}`));
error = checkAppState(app, exports.ISTATE_PENDING_BACKUP);
if (error) return callback(error);
scheduleTask(appId, { /* args */ }, { installationState: exports.ISTATE_PENDING_BACKUP }, (error, result) => {
if (error) return callback(error);