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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user