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

@@ -65,6 +65,15 @@ function debugApp(app) {
debug(app.fqdn + ' ' + util.format.apply(util, Array.prototype.slice.call(arguments, 1)));
}
function makeTaskError(error, app, args) {
let boxError = error instanceof BoxError ? error : new BoxError(BoxError.UNKNOWN_ERROR, error.message); // until we port everything to BoxError
// we stash the args and not task id because
// a) task table is ephemeral
// b) this is actually the state. if we use taskId, we have to track it properly across failed repairs
boxError.details.task = { args, installationState: app.installationState };
return boxError.toPlainObject();
}
// updates the app object and the database
function updateApp(app, values, callback) {
assert.strictEqual(typeof app, 'object');
@@ -74,7 +83,7 @@ function updateApp(app, values, callback) {
debugApp(app, 'updating app with values: %j', values);
appdb.update(app.id, values, function (error) {
if (error) return callback(new BoxError(BoxError.INTERNAL_ERROR, error));
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
for (var value in values) {
app[value] = values[value];
@@ -497,7 +506,7 @@ function install(app, args, progressCallback, callback) {
assert.strictEqual(typeof progressCallback, 'function');
assert.strictEqual(typeof callback, 'function');
const isInstalling = app.installationState !== apps.ISTATE_PENDING_RESTORE; // install or clone
const isInstalling = app.installationState !== apps.ISTATE_PENDING_RESTORE; // install or clone or repair
const restoreConfig = args.restoreConfig || {};
const overwriteDns = args.overwriteDns;
@@ -580,18 +589,19 @@ function install(app, args, progressCallback, callback) {
configureReverseProxy.bind(null, app),
progressCallback.bind(null, { percent: 100, message: 'Done' }),
updateApp.bind(null, app, { installationState: apps.ISTATE_INSTALLED, health: null })
updateApp.bind(null, app, { installationState: apps.ISTATE_INSTALLED, error: null, health: null })
], function seriesDone(error) {
if (error) {
debugApp(app, 'error installing app: %s', error);
return updateApp(app, { installationState: apps.ISTATE_ERROR, error: error.toPlainObject ? error.toPlainObject() : error.message }, callback.bind(null, error));
return updateApp(app, { installationState: apps.ISTATE_ERROR, error: makeTaskError(error, app, args) }, callback.bind(null, error));
}
callback(null);
});
}
function backup(app, progressCallback, callback) {
function backup(app, args, progressCallback, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof args, 'object');
assert.strictEqual(typeof progressCallback, 'function');
assert.strictEqual(typeof callback, 'function');
@@ -613,8 +623,9 @@ function backup(app, progressCallback, callback) {
});
}
function create(app, progressCallback, callback) {
function create(app, args, progressCallback, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof args, 'object');
assert.strictEqual(typeof progressCallback, 'function');
assert.strictEqual(typeof callback, 'function');
@@ -638,7 +649,7 @@ function create(app, progressCallback, callback) {
], function seriesDone(error) {
if (error) {
debugApp(app, 'error creating : %s', error);
return updateApp(app, { installationState: apps.ISTATE_ERROR, error: error.toPlainObject ? error.toPlainObject() : error.message }, callback.bind(null, error));
return updateApp(app, { installationState: apps.ISTATE_ERROR, error: makeTaskError(error, app, args) }, callback.bind(null, error));
}
callback(null);
});
@@ -693,18 +704,21 @@ function changeLocation(app, args, progressCallback, callback) {
], function seriesDone(error) {
if (error) {
debugApp(app, 'error reconfiguring : %s', error);
return updateApp(app, { installationState: apps.ISTATE_ERROR, error: error.toPlainObject ? error.toPlainObject() : error.message }, callback.bind(null, error));
return updateApp(app, { installationState: apps.ISTATE_ERROR, error: makeTaskError(error, app, args) }, callback.bind(null, error));
}
callback(null);
});
}
function migrateDataDir(app, oldDataDir, progressCallback, callback) {
function migrateDataDir(app, args, progressCallback, callback) {
assert.strictEqual(typeof app, 'object');
assert(oldDataDir === null || typeof oldDataDir === 'string');
assert.strictEqual(typeof args, 'object');
assert.strictEqual(typeof progressCallback, 'function');
assert.strictEqual(typeof callback, 'function');
let oldDataDir = args.oldDataDir;
assert(oldDataDir === null || typeof oldDataDir === 'string');
async.series([
progressCallback.bind(null, { percent: 10, message: 'Cleaning up old install' }),
stopApp.bind(null, app, progressCallback),
@@ -737,19 +751,20 @@ function migrateDataDir(app, oldDataDir, progressCallback, callback) {
], function seriesDone(error) {
if (error) {
debugApp(app, 'error reconfiguring : %s', error);
return updateApp(app, { installationState: apps.ISTATE_ERROR, error: error.toPlainObject ? error.toPlainObject() : error.message }, callback.bind(null, error));
return updateApp(app, { installationState: apps.ISTATE_ERROR, error: makeTaskError(error, app, args) }, callback.bind(null, error));
}
callback(null);
});
}
// configure is only called for an infra update
function configure(app, oldConfig, progressCallback, callback) {
function configure(app, args, progressCallback, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof oldConfig, 'object');
assert.strictEqual(typeof args, 'object');
assert.strictEqual(typeof progressCallback, 'function');
assert.strictEqual(typeof callback, 'function');
const oldConfig = args.oldConfig;
const locationChanged = oldConfig.fqdn !== app.fqdn;
const dataDirChanged = oldConfig.dataDir !== app.dataDir;
@@ -819,19 +834,20 @@ function configure(app, oldConfig, progressCallback, callback) {
], function seriesDone(error) {
if (error) {
debugApp(app, 'error reconfiguring : %s', error);
return updateApp(app, { installationState: apps.ISTATE_ERROR, error: error.toPlainObject ? error.toPlainObject() : error.message }, callback.bind(null, error));
return updateApp(app, { installationState: apps.ISTATE_ERROR, error: makeTaskError(error, app, args) }, callback.bind(null, error));
}
callback(null);
});
}
// nginx configuration is skipped because app.httpPort is expected to be available
function update(app, updateConfig, progressCallback, callback) {
function update(app, args, progressCallback, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof updateConfig, 'object');
assert.strictEqual(typeof args, 'object');
assert.strictEqual(typeof progressCallback, 'function');
assert.strictEqual(typeof callback, 'function');
const updateConfig = args.updateConfig;
debugApp(app, `Updating to ${updateConfig.manifest.version}`);
// app does not want these addons anymore
@@ -920,7 +936,7 @@ function update(app, updateConfig, progressCallback, callback) {
updateApp(app, { installationState: apps.ISTATE_INSTALLED, error: null, health: null }, callback.bind(null, error));
} else if (error) {
debugApp(app, 'Error updating app: %s', error);
updateApp(app, { installationState: apps.ISTATE_ERROR, error: error.toPlainObject ? error.toPlainObject() : error.message, updateTime: new Date() }, callback.bind(null, error));
updateApp(app, { installationState: apps.ISTATE_ERROR, error: makeTaskError(error, app, args) }, callback.bind(null, error));
} else {
if (updateConfig.skipNotification) return callback(null);
@@ -929,8 +945,9 @@ function update(app, updateConfig, progressCallback, callback) {
});
}
function uninstall(app, progressCallback, callback) {
function uninstall(app, args, progressCallback, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof args, 'object');
assert.strictEqual(typeof progressCallback, 'function');
assert.strictEqual(typeof callback, 'function');
@@ -973,7 +990,7 @@ function uninstall(app, progressCallback, callback) {
], function seriesDone(error) {
if (error) {
debugApp(app, 'error uninstalling app: %s', error);
return updateApp(app, { installationState: apps.ISTATE_ERROR, error: error.toPlainObject ? error.toPlainObject() : error.message }, callback.bind(null, error));
return updateApp(app, { installationState: apps.ISTATE_ERROR, error: makeTaskError(error, app, args) }, callback.bind(null, error));
}
callback(null);
});
@@ -987,7 +1004,7 @@ function runApp(app, progressCallback, callback) {
progressCallback({ message: 'Starting app' });
docker.startContainer(app.containerId, function (error) {
if (error) return callback(error);
if (error) return callback(new BoxError(BoxError.DOCKER_ERROR, `Error starting container: ${error.message}`));
updateApp(app, { runState: apps.RSTATE_RUNNING }, callback);
});
@@ -1001,7 +1018,7 @@ function stopApp(app, progressCallback, callback) {
progressCallback({ message: 'Stopping app' });
docker.stopContainers(app.id, function (error) {
if (error) return callback(error);
if (error) return callback(new BoxError(BoxError.DOCKER_ERROR, `Error starting container: ${error.message}`));
updateApp(app, { runState: apps.RSTATE_STOPPED, health: null }, callback);
});
@@ -1020,21 +1037,34 @@ function run(appId, args, progressCallback, callback) {
debugApp(app, 'startTask installationState: %s runState: %s', app.installationState, app.runState);
switch (app.installationState) {
case apps.ISTATE_PENDING_INSTALL: return install(app, args, progressCallback, callback);
case apps.ISTATE_PENDING_CONFIGURE:
case apps.ISTATE_PENDING_INSTALL:
case apps.ISTATE_PENDING_CLONE:
case apps.ISTATE_PENDING_RESTORE:
return install(app, args, progressCallback, callback);
case apps.ISTATE_PENDING_REPAIR:
return configure(app, args.oldConfig, progressCallback, callback);
if (app.error.task.installationState === apps.ISTATE_PENDING_UNINSTALL) {
return uninstall(app, app.error.task.args, progressCallback, callback);
} else if (app.error.task.installationState === apps.ISTATE_PENDING_DATA_DIR_MIGRATION) {
return migrateDataDir(app, app.error.task.args, progressCallback, callback);
} else {
return install(app, args, progressCallback, callback);
}
case apps.ISTATE_PENDING_CONFIGURE:
return configure(app, args, progressCallback, callback);
case apps.ISTATE_PENDING_RECREATE_CONTAINER:
case apps.ISTATE_PENDING_RESIZE:
case apps.ISTATE_PENDING_DEBUG:
return create(app, progressCallback, callback);
case apps.ISTATE_PENDING_LOCATION_CHANGE: return changeLocation(app, args, progressCallback, callback);
case apps.ISTATE_PENDING_DATA_DIR_MIGRATION: return migrateDataDir(app, args.oldDataDir, progressCallback, callback);
case apps.ISTATE_PENDING_UNINSTALL: return uninstall(app, progressCallback, callback);
case apps.ISTATE_PENDING_CLONE: return install(app, args, progressCallback, callback);
case apps.ISTATE_PENDING_RESTORE: return install(app, args, progressCallback, callback);
case apps.ISTATE_PENDING_UPDATE: return update(app, args.updateConfig, progressCallback, callback);
case apps.ISTATE_PENDING_BACKUP: return backup(app, progressCallback, callback);
return create(app, args, progressCallback, callback);
case apps.ISTATE_PENDING_LOCATION_CHANGE:
return changeLocation(app, args, progressCallback, callback);
case apps.ISTATE_PENDING_DATA_DIR_MIGRATION:
return migrateDataDir(app, args, progressCallback, callback);
case apps.ISTATE_PENDING_UNINSTALL:
return uninstall(app, args, progressCallback, callback);
case apps.ISTATE_PENDING_UPDATE:
return update(app, args, progressCallback, callback);
case apps.ISTATE_PENDING_BACKUP:
return backup(app, args, progressCallback, callback);
case apps.ISTATE_INSTALLED:
switch (app.runState) {
case apps.RSTATE_PENDING_STOP: return stopApp(app, progressCallback, callback);