fix #344387, remember upgraded version step by step stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Mon, 06 Jul 2009 13:24:10 +0200
branchstable
changeset 2275 bc0bed0616a3
parent 2274 885873dc4361
child 2276 33e7488a407c
fix #344387, remember upgraded version step by step
common/migration.py
common/test/unittest_migration.py
cwctl.py
misc/migration/bootstrapmigration_repository.py
server/migractions.py
--- a/common/migration.py	Mon Jul 06 11:01:41 2009 +0200
+++ b/common/migration.py	Mon Jul 06 13:24:10 2009 +0200
@@ -20,24 +20,6 @@
 from cubicweb import ConfigurationError
 
 
-def migration_files(config, toupgrade):
-    """return an orderer list of path of scripts to execute to upgrade
-    an installed application according to installed cube and cubicweb versions
-    """
-    merged = []
-    for cube, fromversion, toversion in toupgrade:
-        if cube == 'cubicweb':
-            migrdir = config.migration_scripts_dir()
-        else:
-            migrdir = config.cube_migration_scripts_dir(cube)
-        scripts = filter_scripts(config, migrdir, fromversion, toversion)
-        merged += [s[1] for s in scripts]
-    if config.accept_mode('Any'):
-        migrdir = config.migration_scripts_dir()
-        merged.insert(0, join(migrdir, 'bootstrapmigration_repository.py'))
-    return merged
-
-
 def filter_scripts(config, directory, fromversion, toversion, quiet=True):
     """return a list of paths of migration files to consider to upgrade
     from a version to a greater one
@@ -126,6 +108,18 @@
                           'interactive_mode': interactive,
                           }
 
+    def __getattribute__(self, name):
+        try:
+            return object.__getattribute__(self, name)
+        except AttributeError:
+            cmd = 'cmd_%s' % name
+            if hasattr(self, cmd):
+                meth = getattr(self, cmd)
+                return lambda *args, **kwargs: self.interact(args, kwargs,
+                                                             meth=meth)
+            raise
+        raise AttributeError(name)
+
     def repo_connect(self):
         return self.config.repository()
 
@@ -144,31 +138,35 @@
                     return False
                 return orig_accept_mode(mode)
             self.config.accept_mode = accept_mode
-        scripts = migration_files(self.config, toupgrade)
-        if scripts:
-            vmap = dict( (pname, (fromver, tover)) for pname, fromver, tover in toupgrade)
-            self.__context.update({'applcubicwebversion': vcconf['cubicweb'],
-                                   'cubicwebversion': self.config.cubicweb_version(),
-                                   'versions_map': vmap})
-            self.scripts_session(scripts)
-        else:
-            print 'no migration script to execute'
+        # may be an iterator
+        toupgrade = tuple(toupgrade)
+        vmap = dict( (cube, (fromver, tover)) for cube, fromver, tover in toupgrade)
+        ctx = self.__context
+        ctx['versions_map'] = vmap
+        if self.config.accept_mode('Any') and 'cubicweb' in vmap:
+            migrdir = self.config.migration_scripts_dir()
+            self.process_script(join(migrdir, 'bootstrapmigration_repository.py'))
+        for cube, fromversion, toversion in toupgrade:
+            if cube == 'cubicweb':
+                migrdir = self.config.migration_scripts_dir()
+            else:
+                migrdir = self.config.cube_migration_scripts_dir(cube)
+            scripts = filter_scripts(self.config, migrdir, fromversion, toversion)
+            if scripts:
+                for version, script in scripts:
+                    self.process_script(script)
+                    self.cube_upgraded(cube, version)
+                if version != toversion:
+                    self.cube_upgraded(cube, toversion)
+            else:
+                self.cube_upgraded(cube, toversion)
+
+    def cube_upgraded(self, cube, version):
+        pass
 
     def shutdown(self):
         pass
 
-    def __getattribute__(self, name):
-        try:
-            return object.__getattribute__(self, name)
-        except AttributeError:
-            cmd = 'cmd_%s' % name
-            if hasattr(self, cmd):
-                meth = getattr(self, cmd)
-                return lambda *args, **kwargs: self.interact(args, kwargs,
-                                                             meth=meth)
-            raise
-        raise AttributeError(name)
-
     def interact(self, args, kwargs, meth):
         """execute the given method according to user's confirmation"""
         msg = 'execute command: %s(%s) ?' % (
@@ -205,7 +203,6 @@
         if answer in ('r', 'retry'):
             return 2
         if answer in ('a', 'abort'):
-            self.rollback()
             raise SystemExit(1)
         if shell and answer in ('s', 'shell'):
             self.interactive_shell()
@@ -284,16 +281,6 @@
                     return None
                 return func(*args, **kwargs)
 
-    def scripts_session(self, migrscripts):
-        """execute some scripts in a transaction"""
-        try:
-            for migrscript in migrscripts:
-                self.process_script(migrscript)
-            self.commit()
-        except:
-            self.rollback()
-            raise
-
     def cmd_option_renamed(self, oldname, newname):
         """a configuration option has been renamed"""
         self._option_changes.append(('renamed', oldname, newname))
--- a/common/test/unittest_migration.py	Mon Jul 06 11:01:41 2009 +0200
+++ b/common/test/unittest_migration.py	Mon Jul 06 13:24:10 2009 +0200
@@ -13,7 +13,8 @@
 from cubicweb.devtools.apptest import TestEnvironment
 
 from cubicweb.cwconfig import CubicWebConfiguration
-from cubicweb.common.migration import migration_files, filter_scripts
+from cubicweb.common.migration import MigrationHelper, filter_scripts
+from cubicweb.server.migractions import ServerMigrationHelper
 
 
 class Schema(dict):
@@ -39,82 +40,53 @@
         self.config.__class__.cubicweb_vobject_path = frozenset()
         self.config.__class__.cube_vobject_path = frozenset()
 
-    def test_migration_files_base(self):
-        self.assertListEquals(migration_files(self.config, [('cubicweb', (2,3,0), (2,4,0)),
-                                                            ('TEMPLATE', (0,0,2), (0,0,3))]),
-                              [SMIGRDIR+'bootstrapmigration_repository.py',
-                               TMIGRDIR+'0.0.3_Any.py'])
-        self.assertListEquals(migration_files(self.config, [('cubicweb', (2,4,0), (2,5,0)),
-                                                            ('TEMPLATE', (0,0,2), (0,0,3))]),
-                              [SMIGRDIR+'bootstrapmigration_repository.py',
-                               SMIGRDIR+'2.5.0_Any.sql',
-                               TMIGRDIR+'0.0.3_Any.py'])
-        self.assertListEquals(migration_files(self.config, [('cubicweb', (2,5,0), (2,6,0)),
-                                                            ('TEMPLATE', (0,0,3), (0,0,4))]),
-                              [SMIGRDIR+'bootstrapmigration_repository.py',
-                               SMIGRDIR+'2.6.0_Any.sql',
-                               TMIGRDIR+'0.0.4_Any.py'])
+    def test_filter_scripts_base(self):
+        self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,3,0), (2,4,0)),
+                              [])
+        self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,4,0), (2,5,0)),
+                              [((2, 5, 0), SMIGRDIR+'2.5.0_Any.sql')])
+        self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,6,0)),
+                              [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')])
+        self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,4,0), (2,6,0)),
+                              [((2, 5, 0), SMIGRDIR+'2.5.0_Any.sql'),
+                               ((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')])
+        self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,5,1)),
+                              [])
+        self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,10,2)),
+                              [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql'),
+                               ((2, 10, 2), SMIGRDIR+'2.10.2_Any.sql')])
+        self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,1), (2,6,0)),
+                              [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')])
 
-##     def test_migration_files_overlap(self):
-##         self.assertListEquals(migration_files(self.config, (2,4,0), (2,10,2),
-##                                               (0,0,2), (0,1,2)),
-##                               [SMIGRDIR+'bootstrapmigration_repository.py',
-##                                TMIGRDIR+'0.0.3_Any.py',
-##                                TMIGRDIR+'0.0.4_Any.py',
-##                                SMIGRDIR+'2.4.0_2.5.0_Any.sql',
-##                                SMIGRDIR+'2.5.1_2.6.0_Any.sql',
-##                                TMIGRDIR+'0.1.0_Any.py',
-##                                TMIGRDIR+'0.1.0_common.py',
-##                                TMIGRDIR+'0.1.0_repository.py',
-##                                TMIGRDIR+'0.1.2_Any.py',
-##                                SMIGRDIR+'2.10.1_2.10.2_Any.sql'])
+        self.assertListEquals(filter_scripts(self.config, TMIGRDIR, (0,0,2), (0,0,3)),
+                              [((0, 0, 3), TMIGRDIR+'0.0.3_Any.py')])
+        self.assertListEquals(filter_scripts(self.config, TMIGRDIR, (0,0,2), (0,0,4)),
+                              [((0, 0, 3), TMIGRDIR+'0.0.3_Any.py'),
+                               ((0, 0, 4), TMIGRDIR+'0.0.4_Any.py')])
 
-    def test_migration_files_for_mode(self):
-        from cubicweb.server.migractions import ServerMigrationHelper
+    def test_filter_scripts_for_mode(self):
         self.assertIsInstance(self.config.migration_handler(), ServerMigrationHelper)
-        from cubicweb.common.migration import MigrationHelper
         config = CubicWebConfiguration('data')
         config.verbosity = 0
         self.assert_(not isinstance(config.migration_handler(), ServerMigrationHelper))
         self.assertIsInstance(config.migration_handler(), MigrationHelper)
         config = self.config
         config.__class__.name = 'twisted'
-        self.assertListEquals(migration_files(config, [('TEMPLATE', (0,0,4), (0,1,0))]),
-                              [TMIGRDIR+'0.1.0_common.py',
-                               TMIGRDIR+'0.1.0_web.py'])
-        config.__class__.name = 'repository'
-        self.assertListEquals(migration_files(config, [('TEMPLATE', (0,0,4), (0,1,0))]),
-                              [SMIGRDIR+'bootstrapmigration_repository.py',
-                               TMIGRDIR+'0.1.0_Any.py',
-                               TMIGRDIR+'0.1.0_common.py',
-                               TMIGRDIR+'0.1.0_repository.py'])
-        config.__class__.name = 'all-in-one'
-        self.assertListEquals(migration_files(config, [('TEMPLATE', (0,0,4), (0,1,0))]),
-                              [SMIGRDIR+'bootstrapmigration_repository.py',
-                               TMIGRDIR+'0.1.0_Any.py',
-                               TMIGRDIR+'0.1.0_common.py',
-                               TMIGRDIR+'0.1.0_repository.py',
-                               TMIGRDIR+'0.1.0_web.py'])
+        self.assertListEquals(filter_scripts(config, TMIGRDIR, (0,0,4), (0,1,0)),
+                              [((0, 1 ,0), TMIGRDIR+'0.1.0_common.py'),
+                               ((0, 1 ,0), TMIGRDIR+'0.1.0_web.py')])
         config.__class__.name = 'repository'
-
-    def test_filter_scripts(self):
-        self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,4,0), (2,5,0)),
-                              [((2, 5, 0), SMIGRDIR+'2.5.0_Any.sql')])
-        self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,4,0), (2,6,0)),
-                              [((2, 5, 0), SMIGRDIR+'2.5.0_Any.sql'),
-                               ((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')])
-        self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,5,1)),
-                              [])
-        self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,6,0)),
-                              [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')])
-        self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,10,2)),
-                              [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql'),
-                               ((2, 10, 2), SMIGRDIR+'2.10.2_Any.sql')])
-        self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,1), (2,6,0)),
-                              [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')])
-        self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,1), (2,10,2)),
-                              [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql'),
-                               ((2, 10, 2), SMIGRDIR+'2.10.2_Any.sql')])
+        self.assertListEquals(filter_scripts(config, TMIGRDIR, (0,0,4), (0,1,0)),
+                              [((0, 1 ,0), TMIGRDIR+'0.1.0_Any.py'),
+                               ((0, 1 ,0), TMIGRDIR+'0.1.0_common.py'),
+                               ((0, 1 ,0), TMIGRDIR+'0.1.0_repository.py')])
+        config.__class__.name = 'all-in-one'
+        self.assertListEquals(filter_scripts(config, TMIGRDIR, (0,0,4), (0,1,0)),
+                              [((0, 1 ,0), TMIGRDIR+'0.1.0_Any.py'),
+                               ((0, 1 ,0), TMIGRDIR+'0.1.0_common.py'),
+                               ((0, 1 ,0), TMIGRDIR+'0.1.0_repository.py'),
+                               ((0, 1 ,0), TMIGRDIR+'0.1.0_web.py')])
+        config.__class__.name = 'repository'
 
 
 from cubicweb.devtools import ApptestConfiguration, init_test_database, cleanup_sqlite
--- a/cwctl.py	Mon Jul 06 11:01:41 2009 +0200
+++ b/cwctl.py	Mon Jul 06 13:24:10 2009 +0200
@@ -655,7 +655,7 @@
         else:
             applcubicwebversion = vcconf.get('cubicweb')
         if cubicwebversion > applcubicwebversion:
-            toupgrade.append( ('cubicweb', applcubicwebversion, cubicwebversion) )
+            toupgrade.append(('cubicweb', applcubicwebversion, cubicwebversion))
         if not self.config.fs_only and not toupgrade:
             print 'no software migration needed for application %s' % appid
             return
@@ -682,7 +682,6 @@
                            'continue anyway ?'):
                 print 'migration not completed'
                 return
-        mih.rewrite_vcconfiguration()
         mih.shutdown()
         print
         print 'application migrated'
@@ -733,7 +732,8 @@
         config.set_sources_mode(sources)
         mih = config.migration_handler()
         if args:
-            mih.scripts_session(args)
+            for arg in args:
+                mih.process_script(script)
         else:
             mih.interactive_shell()
         mih.shutdown()
--- a/misc/migration/bootstrapmigration_repository.py	Mon Jul 06 11:01:41 2009 +0200
+++ b/misc/migration/bootstrapmigration_repository.py	Mon Jul 06 13:24:10 2009 +0200
@@ -8,6 +8,8 @@
 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 
+applcubicwebversion, cubicwebversion = versions_map['cubicweb']
+
 if applcubicwebversion < (3, 2, 2) and cubicwebversion >= (3, 2, 1):
    from base64 import b64encode
    for table in ('entities', 'deleted_entities'):
--- a/server/migractions.py	Mon Jul 06 11:01:41 2009 +0200
+++ b/server/migractions.py	Mon Jul 06 13:24:10 2009 +0200
@@ -64,25 +64,48 @@
         self.fs_schema = schema
         self._synchronized = set()
 
+    # overriden from base MigrationHelper ######################################
+
     @cached
     def repo_connect(self):
         self.repo = get_repository(method='inmemory', config=self.config)
         return self.repo
 
+    def cube_upgraded(self, cube, version):
+        self.cmd_set_property('system.version.%s' % cube.lower(),
+                              unicode(version))
+        self.commit()
+
     def shutdown(self):
         if self.repo is not None:
             self.repo.shutdown()
 
-    def rewrite_vcconfiguration(self):
-        """write current installed versions (of cubicweb software
-        and of each used cube) into the database
+    def migrate(self, vcconf, toupgrade, options):
+        if not options.fs_only:
+            if options.backup_db is None:
+                self.backup_database()
+            elif options.backup_db:
+                self.backup_database(askconfirm=False)
+        super(ServerMigrationHelper, self).migrate(vcconf, toupgrade, options)
+
+    def process_script(self, migrscript, funcname=None, *args, **kwargs):
+        """execute a migration script
+        in interactive mode,  display the migration script path, ask for
+        confirmation and execute it if confirmed
         """
-        self.cmd_set_property('system.version.cubicweb',
-                              self.config.cubicweb_version())
-        for pkg in self.config.cubes():
-            pkgversion = self.config.cube_version(pkg)
-            self.cmd_set_property('system.version.%s' % pkg.lower(), pkgversion)
-        self.commit()
+        try:
+            if migrscript.endswith('.sql'):
+                if self.execscript_confirm(migrscript):
+                    sqlexec(open(migrscript).read(), self.session.system_sql)
+            else:
+                return super(ServerMigrationHelper, self).process_script(
+                    migrscript, funcname, *args, **kwargs)
+            self.commit()
+        except:
+            self.rollback()
+            raise
+
+    # server specific migration methods ########################################
 
     def backup_database(self, backupfile=None, askconfirm=True):
         config = self.config
@@ -142,26 +165,6 @@
                         break
             print 'database restored'
 
-    def migrate(self, vcconf, toupgrade, options):
-        if not options.fs_only:
-            if options.backup_db is None:
-                self.backup_database()
-            elif options.backup_db:
-                self.backup_database(askconfirm=False)
-        super(ServerMigrationHelper, self).migrate(vcconf, toupgrade, options)
-
-    def process_script(self, migrscript, funcname=None, *args, **kwargs):
-        """execute a migration script
-        in interactive mode,  display the migration script path, ask for
-        confirmation and execute it if confirmed
-        """
-        if migrscript.endswith('.sql'):
-            if self.execscript_confirm(migrscript):
-                sqlexec(open(migrscript).read(), self.session.system_sql)
-        else:
-            return super(ServerMigrationHelper, self).process_script(
-                migrscript, funcname, *args, **kwargs)
-
     @property
     def cnx(self):
         """lazy connection"""