From 8874fb058fbbac810be5f24239b1e54c66a0d1fd Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Tue, 12 Jul 2011 15:33:43 +0200 Subject: [PATCH] [FIX] module.loading: ensure installed modules are all loaded before installing new ones After the recent change to make module install atomically (code *and* data), we ran into issues when installing a new module indirectly triggers code of a not-yet-loaded-but-installed module, via its data that is already in the database (e.g. worflows or reports modified by this module within another module, that now refer to its code). To avoid this, we now make sure that we only install new modules on top of a consistent system (code *and* data), by loading all installed or 'to upgrade' modules *before* starting to install new ones. lp bug: https://launchpad.net/bugs/809168 fixed bzr revid: odo@openerp.com-20110712133343-unf610k23fa6d3pk --- openerp/modules/loading.py | 60 +++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/openerp/modules/loading.py b/openerp/modules/loading.py index fa17ced6daa..6bb45e77598 100644 --- a/openerp/modules/loading.py +++ b/openerp/modules/loading.py @@ -152,6 +152,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules= status = {} processed_modules = [] + loaded_modules = [] statusi = 0 pool = pooler.get_pool(cr.dbname) migrations = openerp.modules.migration.MigrationManager(cr, graph) @@ -169,6 +170,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules= migrations.migrate_module(package, 'pre') register_module_classes(package.name) models = pool.instanciate(package.name, cr) + loaded_modules.append(package.name) if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'): init_module_models(cr, package.name, models) @@ -226,7 +228,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules= cr.commit() - return processed_modules + return loaded_modules, processed_modules def _check_module_names(cr, module_names): mod_names = set(module_names) @@ -242,11 +244,26 @@ def _check_module_names(cr, module_names): incorrect_names = mod_names.difference([x['name'] for x in cr.dictfetchall()]) logging.getLogger('init').warning('invalid module names, ignored: %s', ", ".join(incorrect_names)) +def load_marked_modules(cr, graph, states, force, progressdict, report, loaded_modules): + """Loads modules marked with ``states``, adding them to ``graph`` and + ``loaded_modules`` and returns a list of installed/upgraded modules.""" + processed_modules = [] + while True: + cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(states),)) + module_list = [name for (name,) in cr.fetchall() if name not in graph] + new_modules_in_graph = graph.add_modules(cr, module_list, force) + logger.notifyChannel('init', netsvc.LOG_DEBUG, 'Updating graph with %d more modules' % (len(module_list))) + loaded, processed = load_module_graph(cr, graph, progressdict, report=report, skip_modules=loaded_modules) + processed_modules.extend(processed) + loaded_modules.extend(loaded) + if not processed: break + return processed_modules + + def load_modules(db, force_demo=False, status=None, update_module=False): # TODO status['progress'] reporting is broken: used twice (and reset each # time to zero) in load_module_graph, not fine-grained enough. # It should be a method exposed by the pool. - initialize_sys_path() open_openerp_namespace() @@ -268,10 +285,9 @@ def load_modules(db, force_demo=False, status=None, update_module=False): # This is a brand new pool, just created in pooler.get_db_and_pool() pool = pooler.get_pool(cr.dbname) - processed_modules = [] + processed_modules = [] # for cleanup step after install + loaded_modules = [] # to avoid double loading report = tools.assertion_report() - # NOTE: Try to also load the modules that have been marked as uninstallable previously... - STATES_TO_LOAD = ['installed', 'to upgrade', 'uninstallable'] if 'base' in tools.config['update'] or 'all' in tools.config['update']: cr.execute("update ir_module_module set state=%s where name=%s and state=%s", ('to upgrade', 'base', 'installed')) @@ -281,7 +297,8 @@ def load_modules(db, force_demo=False, status=None, update_module=False): if not graph: logger.notifyChannel('init', netsvc.LOG_CRITICAL, 'module base cannot be loaded! (hint: verify addons-path)') raise osv.osv.except_osv(_('Could not load base module'), _('module base cannot be loaded! (hint: verify addons-path)')) - processed_modules.extend(load_module_graph(cr, graph, status, perform_checks=(not update_module), report=report)) + loaded, processed = load_module_graph(cr, graph, status, perform_checks=(not update_module), report=report) + processed_modules.extend(processed) if tools.config['load_language']: for lang in tools.config['load_language'].split(','): @@ -310,28 +327,19 @@ def load_modules(db, force_demo=False, status=None, update_module=False): cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base')) - STATES_TO_LOAD += ['to install'] - # STEP 3: Load marked modules (skipping base which was done in STEP 1) - loop_guardrail = 0 - while True: - loop_guardrail += 1 - if loop_guardrail > 100: - raise ValueError('Possible recursive module tree detected, aborting.') - cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(STATES_TO_LOAD),)) - - module_list = [name for (name,) in cr.fetchall() if name not in graph] - if not module_list: - break - - new_modules_in_graph = graph.add_modules(cr, module_list, force) - if new_modules_in_graph == 0: - # nothing to load - break - - logger.notifyChannel('init', netsvc.LOG_DEBUG, 'Updating graph with %d more modules' % (len(module_list))) - processed_modules.extend(load_module_graph(cr, graph, status, report=report, skip_modules=processed_modules)) + # IMPORTANT: this is done in two parts, first loading all installed or + # partially installed modules (i.e. installed/to upgrade), to + # offer a consistent system to the second part: installing + # newly selected modules. + states_to_load = ['installed', 'to upgrade'] + processed = load_marked_modules(cr, graph, states_to_load, force, status, report, loaded_modules) + processed_modules.extend(processed) + if update_module: + states_to_load = ['to install'] + processed = load_marked_modules(cr, graph, states_to_load, force, status, report, loaded_modules) + processed_modules.extend(processed) # load custom models cr.execute('select model from ir_model where state=%s', ('manual',))