Use taskId instead of states to check bad state
a) this is because, we have install state and run state. b) we have to put taskId as part of the transaction to prevent race
This commit is contained in:
150
src/apps.js
150
src/apps.js
@@ -322,17 +322,17 @@ function validateDataDir(dataDir) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function getDuplicateErrorDetails(error, location, domainObject, portBindings, alternateDomains) {
|
||||
assert.strictEqual(error.reason, DatabaseError.ALREADY_EXISTS);
|
||||
function getDuplicateErrorDetails(errorMessage, location, domainObject, portBindings, alternateDomains) {
|
||||
assert.strictEqual(typeof errorMessage, 'string');
|
||||
assert.strictEqual(typeof location, 'string');
|
||||
assert.strictEqual(typeof domainObject, 'object');
|
||||
assert.strictEqual(typeof portBindings, 'object');
|
||||
assert(Array.isArray(alternateDomains));
|
||||
|
||||
var match = error.message.match(/ER_DUP_ENTRY: Duplicate entry '(.*)' for key '(.*)'/);
|
||||
var match = errorMessage.match(/ER_DUP_ENTRY: Duplicate entry '(.*)' for key '(.*)'/);
|
||||
if (!match) {
|
||||
debug('Unexpected SQL error message.', error);
|
||||
return new AppsError(AppsError.INTERNAL_ERROR, error);
|
||||
debug('Unexpected SQL error message.', errorMessage);
|
||||
return new AppsError(AppsError.INTERNAL_ERROR, new Error(errorMessage));
|
||||
}
|
||||
|
||||
// check if the location or alternateDomains conflicts
|
||||
@@ -576,15 +576,23 @@ function mailboxNameForLocation(location, manifest) {
|
||||
return (location ? location : manifest.title.toLowerCase().replace(/[^a-zA-Z0-9]/g, '')) + '.app';
|
||||
}
|
||||
|
||||
function scheduleTask(appId, args, callback) {
|
||||
function scheduleTask(appId, args, values, callback) {
|
||||
assert.strictEqual(typeof appId, 'string');
|
||||
assert.strictEqual(typeof args, 'object');
|
||||
assert.strictEqual(typeof values, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
assert(values.installationState || values.runState);
|
||||
|
||||
tasks.add(tasks.TASK_APP, [ appId, args ], function (error, taskId) {
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
|
||||
appdb.update(appId, { taskId: taskId }, function (error) {
|
||||
values.errorMessage = '';
|
||||
values.taskId = taskId;
|
||||
|
||||
appdb.setTask(appId, values, function (error) {
|
||||
if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(new AppsError(AppsError.ALREADY_EXISTS, error.message));
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.BAD_STATE));
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
|
||||
appTaskManager.scheduleTask(appId, taskId, function (error) {
|
||||
@@ -713,7 +721,7 @@ function install(data, user, auditSource, callback) {
|
||||
};
|
||||
|
||||
appdb.add(appId, appStoreId, manifest, location, domain, translatePortBindings(portBindings, manifest), data, function (error) {
|
||||
if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(getDuplicateErrorDetails(error, location, domainObject, portBindings, data.alternateDomains));
|
||||
if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(getDuplicateErrorDetails(error.message, location, domainObject, portBindings, data.alternateDomains));
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND, error.message));
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
|
||||
@@ -728,7 +736,7 @@ function install(data, user, auditSource, callback) {
|
||||
|
||||
const restoreConfig = backupId ? { backupId: backupId, backupFormat: backupFormat } : null;
|
||||
|
||||
scheduleTask(appId, { restoreConfig }, function (error, result) {
|
||||
scheduleTask(appId, { restoreConfig }, { installationState: appdb.ISTATE_PENDING_INSTALL }, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
eventlog.add(eventlog.ACTION_APP_INSTALL, auditSource, { appId: appId, app: result });
|
||||
@@ -750,9 +758,10 @@ function configure(appId, data, user, auditSource, callback) {
|
||||
|
||||
get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
if (app.installationState !== appdb.ISTATE_INSTALLED && app.installationState !== appdb.ISTATE_ERROR) return callback(new AppsError(AppsError.BAD_STATE, `Not allowed in this app state : ${app.installationState}`));
|
||||
if (app.taskId) return callback(new AppsError(AppsError.BAD_STATE, `Not allowed in this app state : ${app.installationState} / ${app.runState}`));
|
||||
|
||||
let domain, location, portBindings, values = { installationState: appdb.ISTATE_PENDING_CONFIGURE };
|
||||
|
||||
let domain, location, portBindings, values = { };
|
||||
if ('location' in data && 'domain' in data) {
|
||||
location = values.location = data.location.toLowerCase();
|
||||
domain = values.domain = data.domain.toLowerCase();
|
||||
@@ -870,18 +879,13 @@ function configure(appId, data, user, auditSource, callback) {
|
||||
|
||||
debug(`configure: id:${appId}`);
|
||||
|
||||
appdb.setInstallationCommand(appId, appdb.ISTATE_PENDING_CONFIGURE, values, function (error) {
|
||||
if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(getDuplicateErrorDetails(error, location, domainObject, portBindings, data.alternateDomains));
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.BAD_STATE));
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
scheduleTask(appId, { oldConfig: getAppConfig(app) }, values, function (error, result) {
|
||||
if (error && error.reason === AppsError.ALREADY_EXISTS) error = getDuplicateErrorDetails(error.message, location, domainObject, portBindings, data.alternateDomains);
|
||||
if (error) return callback(error);
|
||||
|
||||
scheduleTask(appId, { oldConfig: getAppConfig(app) }, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
eventlog.add(eventlog.ACTION_APP_CONFIGURE, auditSource, { appId: appId, app: result });
|
||||
|
||||
eventlog.add(eventlog.ACTION_APP_CONFIGURE, auditSource, { appId: appId, app: result });
|
||||
|
||||
callback(null, { taskId: result.taskId });
|
||||
});
|
||||
callback(null, { taskId: result.taskId });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -897,7 +901,7 @@ function update(appId, data, auditSource, callback) {
|
||||
|
||||
get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
if (app.installationState !== appdb.ISTATE_INSTALLED && app.installationState !== appdb.ISTATE_ERROR) return callback(new AppsError(AppsError.BAD_STATE, `Not allowed in this app state : ${app.installationState}`));
|
||||
if (app.taskId) return callback(new AppsError(AppsError.BAD_STATE, `Not allowed in this app state : ${app.installationState} / ${app.runState}`));
|
||||
|
||||
downloadManifest(data.appStoreId, data.manifest, function (error, appStoreId, manifest) {
|
||||
if (error) return callback(error);
|
||||
@@ -951,20 +955,15 @@ function update(appId, data, auditSource, callback) {
|
||||
updateConfig.memoryLimit = updateConfig.manifest.memoryLimit;
|
||||
}
|
||||
|
||||
appdb.setInstallationCommand(appId, appdb.ISTATE_PENDING_UPDATE, { }, function (error) {
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.BAD_STATE)); // might be a bad guess
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
scheduleTask(appId, { updateConfig: updateConfig }, { installationState: appdb.ISTATE_PENDING_UPDATE }, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
scheduleTask(appId, { updateConfig: updateConfig }, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
eventlog.add(eventlog.ACTION_APP_UPDATE, auditSource, { appId: appId, toManifest: manifest, fromManifest: app.manifest, force: data.force, app: app });
|
||||
|
||||
eventlog.add(eventlog.ACTION_APP_UPDATE, auditSource, { appId: appId, toManifest: manifest, fromManifest: app.manifest, force: data.force, app: app });
|
||||
// clear update indicator, if update fails, it will come back through the update checker
|
||||
updateChecker.resetAppUpdateInfo(appId);
|
||||
|
||||
// clear update indicator, if update fails, it will come back through the update checker
|
||||
updateChecker.resetAppUpdateInfo(appId);
|
||||
|
||||
callback(null, { taskId: result.taskId });
|
||||
});
|
||||
callback(null, { taskId: result.taskId });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1034,7 +1033,7 @@ function restore(appId, data, auditSource, callback) {
|
||||
|
||||
get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
if (app.installationState !== appdb.ISTATE_INSTALLED && app.installationState !== appdb.ISTATE_ERROR) return callback(new AppsError(AppsError.BAD_STATE, `Not allowed in this app state : ${app.installationState}`));
|
||||
if (app.taskId) return callback(new AppsError(AppsError.BAD_STATE, `Not allowed in this app state : ${app.installationState} / ${app.runState}`));
|
||||
|
||||
// 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 }); };
|
||||
@@ -1050,22 +1049,18 @@ function restore(appId, data, auditSource, callback) {
|
||||
error = checkManifestConstraints(backupInfo.manifest);
|
||||
if (error) return callback(error);
|
||||
|
||||
var values = {
|
||||
let values = {
|
||||
installationState: appdb.ISTATE_PENDING_RESTORE,
|
||||
manifest: backupInfo.manifest
|
||||
};
|
||||
|
||||
appdb.setInstallationCommand(appId, appdb.ISTATE_PENDING_RESTORE, values, function (error) {
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.BAD_STATE)); // might be a bad guess
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
const restoreConfig = data.backupId ? { backupId: data.backupId, backupFormat: backupInfo.format, oldManifest: app.manifest } : null; // when null, apptask simply reinstalls
|
||||
scheduleTask(appId, { restoreConfig }, values, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
const restoreConfig = data.backupId ? { backupId: data.backupId, backupFormat: backupInfo.format, oldManifest: app.manifest } : null; // when null, apptask simply reinstalls
|
||||
scheduleTask(appId, { restoreConfig }, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
eventlog.add(eventlog.ACTION_APP_RESTORE, auditSource, { app: app, backupId: backupInfo.id, fromManifest: app.manifest, toManifest: backupInfo.manifest });
|
||||
|
||||
eventlog.add(eventlog.ACTION_APP_RESTORE, auditSource, { app: app, backupId: backupInfo.id, fromManifest: app.manifest, toManifest: backupInfo.manifest });
|
||||
|
||||
callback(null, { taskId: result.taskId });
|
||||
});
|
||||
callback(null, { taskId: result.taskId });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1161,7 +1156,7 @@ function clone(appId, data, user, auditSource, callback) {
|
||||
};
|
||||
|
||||
appdb.add(newAppId, app.appStoreId, manifest, location, domain, translatePortBindings(portBindings, manifest), data, function (error) {
|
||||
if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(getDuplicateErrorDetails(error, location, domainObject, portBindings, []));
|
||||
if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(getDuplicateErrorDetails(error.message, location, domainObject, portBindings, []));
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
|
||||
purchaseApp({ appId: newAppId, appstoreId: app.appStoreId, manifestId: manifest.id }, function (error) {
|
||||
@@ -1169,7 +1164,7 @@ function clone(appId, data, user, auditSource, callback) {
|
||||
|
||||
const restoreConfig = { backupId: backupId, backupFormat: backupInfo.format };
|
||||
|
||||
scheduleTask(newAppId, { restoreConfig }, function (error, result) {
|
||||
scheduleTask(newAppId, { restoreConfig }, { installationState: appdb.ISTATE_PENDING_CLONE }, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
eventlog.add(eventlog.ACTION_APP_CLONE, auditSource, { appId: newAppId, oldAppId: appId, backupId: backupId, oldApp: app, newApp: result });
|
||||
@@ -1192,7 +1187,7 @@ function uninstall(appId, auditSource, callback) {
|
||||
|
||||
get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
if (app.installationState !== appdb.ISTATE_INSTALLED && app.installationState !== appdb.ISTATE_ERROR) return callback(new AppsError(AppsError.BAD_STATE, `Not allowed in this app state : ${app.installationState}`));
|
||||
if (app.taskId) return callback(new AppsError(AppsError.BAD_STATE, `Not allowed in this app state : ${app.installationState} / ${app.runState}`));
|
||||
|
||||
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));
|
||||
@@ -1201,17 +1196,12 @@ function uninstall(appId, auditSource, callback) {
|
||||
if (error && error.reason === AppstoreError.EXTERNAL_ERROR) return callback(new AppsError(AppsError.EXTERNAL_ERROR, error.message));
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
|
||||
appdb.update(appId, { installationState: appdb.ISTATE_PENDING_UNINSTALL }, function (error) {
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND, 'No such app'));
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
scheduleTask(appId, { /* args */ }, { installationState: appdb.ISTATE_PENDING_UNINSTALL }, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
scheduleTask(appId, {}, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
eventlog.add(eventlog.ACTION_APP_UNINSTALL, auditSource, { appId: appId, app: result });
|
||||
|
||||
eventlog.add(eventlog.ACTION_APP_UNINSTALL, auditSource, { appId: appId, app: result });
|
||||
|
||||
callback(null, { taskId: result.taskId });
|
||||
});
|
||||
callback(null, { taskId: result.taskId });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1225,15 +1215,9 @@ function start(appId, callback) {
|
||||
|
||||
get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
if (app.installationState !== appdb.ISTATE_INSTALLED) return callback(new AppsError(AppsError.BAD_STATE, `Not allowed in this app state : ${app.installationState}`));
|
||||
if (app.runState !== appdb.RSTATE_STOPPED) return callback(new AppsError(AppsError.BAD_STATE, `Not allowed in this app state : ${app.installationState}`));
|
||||
if (app.taskId) return callback(new AppsError(AppsError.BAD_STATE, `Not allowed in this app state : ${app.installationState} / ${app.runState}`));
|
||||
|
||||
appdb.setRunCommand(appId, appdb.RSTATE_PENDING_START, function (error) {
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.BAD_STATE)); // might be a bad guess
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
|
||||
scheduleTask(appId, {}, callback);
|
||||
});
|
||||
scheduleTask(appId, { /* args */ }, { runState: appdb.RSTATE_PENDING_START }, callback);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1245,15 +1229,9 @@ function stop(appId, callback) {
|
||||
|
||||
get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
if (app.installationState !== appdb.ISTATE_INSTALLED) return callback(new AppsError(AppsError.BAD_STATE, `Not allowed in this app state : ${app.installationState}`));
|
||||
if (app.runState !== appdb.RSTATE_RUNNING) return callback(new AppsError(AppsError.BAD_STATE, `Not allowed in this app state : ${app.installationState}`));
|
||||
if (app.taskId) return callback(new AppsError(AppsError.BAD_STATE, `Not allowed in this app state : ${app.installationState} / ${app.runState}`));
|
||||
|
||||
appdb.setRunCommand(appId, appdb.RSTATE_PENDING_STOP, function (error) {
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.BAD_STATE)); // might be a bad guess
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
|
||||
scheduleTask(appId, {}, callback);
|
||||
});
|
||||
scheduleTask(appId, { /* args */ }, { runState: appdb.RSTATE_PENDING_STOP }, callback);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1393,24 +1371,18 @@ function backup(appId, callback) {
|
||||
assert.strictEqual(typeof appId, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
appdb.exists(appId, function (error, exists) {
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
if (!exists) return callback(new AppsError(AppsError.NOT_FOUND));
|
||||
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}`));
|
||||
|
||||
appdb.setInstallationCommand(appId, appdb.ISTATE_PENDING_BACKUP, function (error) {
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.BAD_STATE)); // might be a bad guess
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
scheduleTask(appId, { /* args */ }, { installationState: appdb.ISTATE_PENDING_BACKUP }, (error, result) => {
|
||||
if (error) return callback(error);
|
||||
|
||||
scheduleTask(appId, { }, (error, result) => {
|
||||
if (error) return callback(error);
|
||||
|
||||
callback(null, { taskId: result.taskId });
|
||||
});
|
||||
callback(null, { taskId: result.taskId });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function listBackups(page, perPage, appId, callback) {
|
||||
assert(typeof page === 'number' && page > 0);
|
||||
assert(typeof perPage === 'number' && perPage > 0);
|
||||
@@ -1442,10 +1414,10 @@ function restoreInstalledApps(callback) {
|
||||
|
||||
debug(`marking ${app.fqdn} for restore using restore config ${JSON.stringify(restoreConfig)}`);
|
||||
|
||||
appdb.setInstallationCommand(app.id, appdb.ISTATE_PENDING_RESTORE, { restoreConfig: restoreConfig }, function (error) {
|
||||
appdb.update(app.id, { taskId: null }, function (error) { // clear any stale taskId
|
||||
if (error) debug(`Error marking ${app.fqdn} for restore: ${JSON.stringify(error)}`);
|
||||
|
||||
scheduleTask(app.id, { restoreConfig }, () => iteratorDone()); // always succeed
|
||||
scheduleTask(app.id, { restoreConfig }, { installationState: appdb.ISTATE_PENDING_RESTORE }, () => iteratorDone()); // always succeed
|
||||
});
|
||||
});
|
||||
}, callback);
|
||||
@@ -1461,10 +1433,10 @@ function configureInstalledApps(callback) {
|
||||
async.map(apps, function (app, iteratorDone) {
|
||||
debug(`marking ${app.fqdn} for reconfigure`);
|
||||
|
||||
appdb.setInstallationCommand(app.id, appdb.ISTATE_PENDING_CONFIGURE, { }, function (error) {
|
||||
appdb.update(app.id, { taskId: null }, function (error) { // clear any stale taskId
|
||||
if (error) debug(`Error marking ${app.fqdn} for reconfigure: ${JSON.stringify(error)}`);
|
||||
|
||||
scheduleTask(app.id, { oldConfig: getAppConfig(app) }, () => iteratorDone()); // always succeed
|
||||
scheduleTask(app.id, { oldConfig: getAppConfig(app) }, { installationState: appdb.ISTATE_PENDING_CONFIGURE }, () => iteratorDone()); // always succeed
|
||||
});
|
||||
}, callback);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user