[FIX] module.loading: ensure installed modules are all loaded before installing new...
authorOlivier Dony <odo@openerp.com>
Tue, 12 Jul 2011 13:33:43 +0000 (15:33 +0200)
committerOlivier Dony <odo@openerp.com>
Tue, 12 Jul 2011 13:33:43 +0000 (15:33 +0200)
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

index fa17ced..6bb45e7 100644 (file)
@@ -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',))