← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~wgrant/launchpad/death-of-ztm into lp:launchpad

 

William Grant has proposed merging lp:~wgrant/launchpad/death-of-ztm into lp:launchpad with lp:~wgrant/launchpad/separate-zopeless-mail as a prerequisite.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #396419 in Launchpad itself: "Add switchDbUser() to Launchpad's DatabaseLayer"
  https://bugs.launchpad.net/launchpad/+bug/396419
  Bug #484033 in Launchpad itself: "initZopeless should die"
  https://bugs.launchpad.net/launchpad/+bug/484033

For more details, see:
https://code.launchpad.net/~wgrant/launchpad/death-of-ztm/+merge/76926

This branch removes all remaining Zopeless cruft except for the test layers, fixing bug #484033.

There are three separate but somewhat intertwined changes here. Effectively splitting them would have been somewhere between difficult, ugly and impossible.

Firstly, LaunchpadScript has been ported to use DatabaseConfig.override directly, dropping its initZopeless dependency. Some scripts specified custom isolation levels: some of these have been dropped (read_committed has been the default for scripts forever; overriding to that was pointless), while the rest have had the ISOLATION_LEVEL_* psycopg2 symbols replaced with the strings expected by the LP database adapters. initZopeless used to do this mapping itself, for compatibility.

Secondly, the test infrastructure around switching to other database users has been ported to no longer depend on Zopeless. This allowed ZopelessTransactionManager._reset_stores, probably our most Storm-violating method, to be moved to lp.testing.dbuser -- well out of production code. LaunchpadZopelessLayer.switchDbUser is left as a shim around the new lp.testing.dbuser.switch_dbuser. The only noticeable change here is that switchDbUser now commits before switching; this broke two doctests that didn't explicitly abort the transaction before switching users, and expected the changes to be thrown away.

Lastly, ZopelessTransactionManager has finally been sent onto its next life, taking initZopeless with it.
-- 
https://code.launchpad.net/~wgrant/launchpad/death-of-ztm/+merge/76926
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wgrant/launchpad/death-of-ztm into lp:launchpad.
=== modified file 'cronscripts/distributionmirror-prober.py'
--- cronscripts/distributionmirror-prober.py	2011-02-20 04:55:42 +0000
+++ cronscripts/distributionmirror-prober.py	2011-09-26 00:50:29 +0000
@@ -12,7 +12,6 @@
 import os
 
 from canonical.config import config
-from canonical.database.sqlbase import ISOLATION_LEVEL_AUTOCOMMIT
 from lp.services.scripts.base import (
     LaunchpadCronScript,
     LaunchpadScriptFailure,
@@ -67,4 +66,4 @@
     script = DistroMirrorProberScript(
         'distributionmirror-prober',
         dbuser=config.distributionmirrorprober.dbuser)
-    script.lock_and_run(isolation=ISOLATION_LEVEL_AUTOCOMMIT)
+    script.lock_and_run(isolation='autocommit')

=== modified file 'cronscripts/foaf-update-karma-cache.py'
--- cronscripts/foaf-update-karma-cache.py	2011-02-21 00:06:55 +0000
+++ cronscripts/foaf-update-karma-cache.py	2011-09-26 00:50:29 +0000
@@ -12,7 +12,6 @@
 from canonical.config import config
 from canonical.database.sqlbase import (
     cursor,
-    ISOLATION_LEVEL_AUTOCOMMIT,
     flush_database_updates,
     )
 from lp.app.errors import NotFoundError
@@ -295,4 +294,4 @@
     # COMMIT all the time. However, if we interrupt this script mid-run
     # it will need to be re-run as the data will be inconsistent (only
     # part of the caches will have been recalculated).
-    script.lock_and_run(isolation=ISOLATION_LEVEL_AUTOCOMMIT)
+    script.lock_and_run(isolation='autocommit')

=== modified file 'cronscripts/librarian-gc.py'
--- cronscripts/librarian-gc.py	2011-09-18 08:08:36 +0000
+++ cronscripts/librarian-gc.py	2011-09-26 00:50:29 +0000
@@ -18,7 +18,6 @@
 import logging
 
 from canonical.config import config
-from canonical.database.sqlbase import ISOLATION_LEVEL_AUTOCOMMIT
 from canonical.launchpad.database.librarian import LibraryFileAlias
 from canonical.launchpad.interfaces.lpstorm import IStore
 from canonical.librarian import librariangc
@@ -95,5 +94,5 @@
 if __name__ == '__main__':
     script = LibrarianGC('librarian-gc',
                          dbuser=config.librarian_gc.dbuser)
-    script.lock_and_run(isolation=ISOLATION_LEVEL_AUTOCOMMIT)
+    script.lock_and_run(isolation='autocommit')
 

=== modified file 'cronscripts/oops-prune.py'
--- cronscripts/oops-prune.py	2011-02-20 04:55:42 +0000
+++ cronscripts/oops-prune.py	2011-09-26 00:50:29 +0000
@@ -13,7 +13,6 @@
 import os
 
 from canonical.config import config
-from canonical.database.sqlbase import ISOLATION_LEVEL_AUTOCOMMIT
 from lp.services.scripts.base import (
     LaunchpadCronScript, LaunchpadScriptFailure)
 from canonical.launchpad.scripts.oops import (
@@ -54,4 +53,4 @@
 
 if __name__ == '__main__':
     script = OOPSPruner('oops-prune', dbuser='oopsprune')
-    script.lock_and_run(isolation=ISOLATION_LEVEL_AUTOCOMMIT)
+    script.lock_and_run(isolation='autocommit')

=== modified file 'cronscripts/rosetta-export-queue.py'
--- cronscripts/rosetta-export-queue.py	2011-02-20 04:55:42 +0000
+++ cronscripts/rosetta-export-queue.py	2011-09-26 00:50:29 +0000
@@ -7,7 +7,6 @@
 
 import _pythonpath
 
-from canonical.database.sqlbase import ISOLATION_LEVEL_READ_COMMITTED
 from canonical.launchpad.webapp.dbpolicy import SlaveDatabasePolicy
 from lp.translations.scripts.po_export_queue import process_queue
 from lp.services.scripts.base import LaunchpadCronScript
@@ -23,4 +22,4 @@
 
 if __name__ == '__main__':
     script = RosettaExportQueue('rosetta-export-queue', dbuser='poexport')
-    script.lock_and_run(isolation=ISOLATION_LEVEL_READ_COMMITTED)
+    script.lock_and_run()

=== modified file 'cronscripts/update-stats.py'
--- cronscripts/update-stats.py	2011-02-20 04:55:42 +0000
+++ cronscripts/update-stats.py	2011-09-26 00:50:29 +0000
@@ -10,7 +10,6 @@
 import _pythonpath
 
 from zope.component import getUtility
-from canonical.database.sqlbase import ISOLATION_LEVEL_READ_COMMITTED
 from canonical.launchpad.interfaces.launchpadstatistic import (
     ILaunchpadStatisticSet,
     )
@@ -42,4 +41,4 @@
 
 if __name__ == '__main__':
     script = StatUpdater('launchpad-stats', dbuser=config.statistician.dbuser)
-    script.lock_and_run(isolation=ISOLATION_LEVEL_READ_COMMITTED)
+    script.lock_and_run()

=== modified file 'daemons/buildd-manager.tac'
--- daemons/buildd-manager.tac	2011-09-26 00:50:29 +0000
+++ daemons/buildd-manager.tac	2011-09-26 00:50:29 +0000
@@ -8,8 +8,7 @@
 from twisted.scripts.twistd import ServerOptions
 from twisted.web import server
 
-from canonical.config import config
-from canonical.database.sqlbase import ZopelessTransactionManager
+from canonical.config import dbconfig
 from canonical.launchpad.daemons import readyservice
 from canonical.launchpad.scripts import execute_zcml_for_scripts
 from lp.buildmaster.manager import BuilddManager
@@ -17,7 +16,7 @@
 from lp.services.twistedsupport.loggingsupport import RotatableFileLogObserver
 
 execute_zcml_for_scripts()
-ZopelessTransactionManager.initZopeless(dbuser='buildd_manager')
+dbconfig.override(dbuser='buildd_manager', isolation_level='read_committed')
 # XXX wgrant 2011-09-24 bug=29744: initZopeless used to do this.
 # Should be removed from callsites verified to not need it.
 set_immediate_mail_delivery(True)

=== modified file 'lib/canonical/database/ftests/script_isolation.py'
--- lib/canonical/database/ftests/script_isolation.py	2011-09-19 04:56:24 +0000
+++ lib/canonical/database/ftests/script_isolation.py	2011-09-26 00:50:29 +0000
@@ -18,15 +18,14 @@
 
 import transaction
 
-from canonical.database.sqlbase import (
-    cursor,
-    ISOLATION_LEVEL_SERIALIZABLE,
-    ZopelessTransactionManager,
-    )
+from canonical.config import dbconfig
+from canonical.database.sqlbase import cursor
 from canonical.launchpad.scripts import execute_zcml_for_scripts
+from canonical.testing.layers import disconnect_stores
 
 execute_zcml_for_scripts()
 
+
 def check():
     cur = cursor()
     cur.execute("UPDATE Person SET homepage_content='foo' WHERE name='mark'")
@@ -41,13 +40,10 @@
     cur.execute("SHOW transaction_isolation")
     print cur.fetchone()[0]
 
-# First confirm the default isolation level
-ZopelessTransactionManager.initZopeless(dbuser='launchpad_main')
+dbconfig.override(dbuser='launchpad_main', isolation_level='read_committed')
+disconnect_stores()
 check()
-ZopelessTransactionManager.uninstall()
 
-ZopelessTransactionManager.initZopeless(
-    dbuser='launchpad_main',
-    isolation=ISOLATION_LEVEL_SERIALIZABLE)
+dbconfig.override(isolation_level='serializable')
+disconnect_stores()
 check()
-ZopelessTransactionManager.uninstall()

=== modified file 'lib/canonical/database/ftests/test_doctests.py'
--- lib/canonical/database/ftests/test_doctests.py	2010-10-04 19:50:45 +0000
+++ lib/canonical/database/ftests/test_doctests.py	2011-09-26 00:50:29 +0000
@@ -13,8 +13,6 @@
     return unittest.TestSuite([
             LayeredDocFileSuite(
                 'test_multitablecopy.txt',
-                'test_zopelesstransactionmanager.txt',
                 'test_sqlbaseconnect.txt',
                 layer=LaunchpadScriptLayer, stdout_logging=False),
             ])
-

=== modified file 'lib/canonical/database/ftests/test_isolation.py'
--- lib/canonical/database/ftests/test_isolation.py	2011-09-18 08:15:00 +0000
+++ lib/canonical/database/ftests/test_isolation.py	2011-09-26 00:50:29 +0000
@@ -14,17 +14,21 @@
 
 import transaction
 
+from canonical.config import dbconfig
 from canonical.database.sqlbase import (
-    cursor, ISOLATION_LEVEL_AUTOCOMMIT, ISOLATION_LEVEL_DEFAULT,
-    ISOLATION_LEVEL_READ_COMMITTED, ISOLATION_LEVEL_SERIALIZABLE,
-    connect, ZopelessTransactionManager)
-from canonical.testing.layers import LaunchpadZopelessLayer
+    cursor,
+    connect,
+    ISOLATION_LEVEL_SERIALIZABLE,
+    )
+from canonical.testing.layers import (
+    disconnect_stores,
+    LaunchpadZopelessLayer,
+    )
 
 
 def set_isolation_level(isolation):
-    user = ZopelessTransactionManager._dbuser
-    ZopelessTransactionManager.uninstall()
-    ZopelessTransactionManager.initZopeless(dbuser=user, isolation=isolation)
+    dbconfig.override(isolation_level=isolation)
+    disconnect_stores()
 
 
 class TestIsolation(unittest.TestCase):
@@ -42,12 +46,8 @@
     def test_default(self):
         self.failUnlessEqual(self.getCurrentIsolation(), 'read committed')
 
-    def test_default2(self):
-        set_isolation_level(ISOLATION_LEVEL_DEFAULT)
-        self.failUnlessEqual(self.getCurrentIsolation(), 'read committed')
-
     def test_autocommit(self):
-        set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
+        set_isolation_level('autocommit')
         # There is no actual 'autocommit' mode in PostgreSQL. psycopg
         # implements this feature by using read committed isolation and
         # issuing commit() statements after every query.
@@ -67,17 +67,17 @@
         self.failUnlessEqual(cur.fetchone()[0], 0)
 
     def test_readCommitted(self):
-        set_isolation_level(ISOLATION_LEVEL_READ_COMMITTED)
+        set_isolation_level('read_committed')
         self.failUnlessEqual(self.getCurrentIsolation(), 'read committed')
 
     def test_serializable(self):
-        set_isolation_level(ISOLATION_LEVEL_SERIALIZABLE)
+        set_isolation_level('serializable')
         self.failUnlessEqual(self.getCurrentIsolation(), 'serializable')
 
     def test_commit(self):
         # Change the isolation level
         self.failUnlessEqual(self.getCurrentIsolation(), 'read committed')
-        set_isolation_level(ISOLATION_LEVEL_SERIALIZABLE)
+        set_isolation_level('serializable')
         self.failUnlessEqual(self.getCurrentIsolation(), 'serializable')
 
         cur = cursor()
@@ -89,7 +89,7 @@
     def test_rollback(self):
         # Change the isolation level
         self.failUnlessEqual(self.getCurrentIsolation(), 'read committed')
-        set_isolation_level(ISOLATION_LEVEL_SERIALIZABLE)
+        set_isolation_level('serializable')
         self.failUnlessEqual(self.getCurrentIsolation(), 'serializable')
 
         cur = cursor()

=== removed file 'lib/canonical/database/ftests/test_zopelesstransactionmanager.txt'
--- lib/canonical/database/ftests/test_zopelesstransactionmanager.txt	2011-09-19 04:09:21 +0000
+++ lib/canonical/database/ftests/test_zopelesstransactionmanager.txt	1970-01-01 00:00:00 +0000
@@ -1,57 +0,0 @@
-= ZopelessTransactionManager =
-
-The ZopelessTransactionManager used to be an alternative to the Zope
-transaction manager and SQLOS database adapter.
-
-With the move to Storm, it was converted to a small compatibility shim
-used to configure the database adapter.
-
-== Initialization ==
-
-The ZopelessTransactionManager is initialized with its initZopeless()
-class method.
-
-    >>> from canonical.database.sqlbase import ZopelessTransactionManager
-    >>> ZopelessTransactionManager.initZopeless(dbuser='launchpad_main')
-
-After initZopeless() has been called, the '_installed' attribute of
-ZopelessTransactionManager will be set to the transaction manager:
-
-    >>> ZopelessTransactionManager._installed is ZopelessTransactionManager
-    True
-
-The initZopeless() call defaults to read committed isolation:
-
-    >>> from canonical.database.sqlbase import cursor
-    >>> c = cursor()
-    >>> c.execute("SHOW transaction_isolation")
-    >>> print c.fetchone()[0]
-    read committed
-
-The uninstall() method can be used to uninstall the transaction
-manager:
-
-    >>> ZopelessTransactionManager.uninstall()
-    >>> print ZopelessTransactionManager._installed
-    None
-
-We can log in as alternative users with initZopeless():
-
-    >>> ZopelessTransactionManager.initZopeless(dbuser='testadmin')
-    >>> c = cursor()
-    >>> c.execute("SELECT current_user")
-    >>> print c.fetchone()[0]
-    testadmin
-    >>> ZopelessTransactionManager.uninstall()
-
-Or we can specify other transaction isolation modes:
-
-    >>> from canonical.database.sqlbase import (
-    ...     ISOLATION_LEVEL_SERIALIZABLE)
-    >>> ZopelessTransactionManager.initZopeless(
-    ...     dbuser='librarian', isolation=ISOLATION_LEVEL_SERIALIZABLE)
-    >>> c = cursor()
-    >>> c.execute("SHOW transaction_isolation")
-    >>> print c.fetchone()[0]
-    serializable
-    >>> ZopelessTransactionManager.uninstall()

=== modified file 'lib/canonical/database/sqlbase.py'
--- lib/canonical/database/sqlbase.py	2011-09-20 11:58:11 +0000
+++ lib/canonical/database/sqlbase.py	2011-09-26 00:50:29 +0000
@@ -25,7 +25,6 @@
     'SQLBase',
     'sqlvalues',
     'StupidCache',
-    'ZopelessTransactionManager',
     ]
 
 
@@ -262,75 +261,6 @@
         clear_property_cache(self)
 
 
-class ZopelessTransactionManager(object):
-    """Compatibility shim for initZopeless()"""
-
-    _installed = None
-
-    def __init__(self):
-        raise AssertionError("ZopelessTransactionManager should not be "
-                             "directly instantiated.")
-
-    @classmethod
-    def initZopeless(cls, dbuser=None, isolation=ISOLATION_LEVEL_DEFAULT):
-        if dbuser is None:
-            raise AssertionError(
-                "dbuser is now required. All scripts must connect as unique "
-                "database users.")
-
-        isolation_level = {
-            ISOLATION_LEVEL_AUTOCOMMIT: 'autocommit',
-            ISOLATION_LEVEL_READ_COMMITTED: 'read_committed',
-            ISOLATION_LEVEL_SERIALIZABLE: 'serializable'}[isolation]
-
-        dbconfig.override(dbuser=dbuser, isolation_level=isolation_level)
-
-        cls._dbuser = dbuser
-        cls._isolation = isolation
-        cls._reset_stores()
-        cls._installed = cls
-
-    @staticmethod
-    def _reset_stores():
-        """Reset the active stores.
-
-        This is required for connection setting changes to be made visible.
-        """
-        for name, store in getUtility(IZStorm).iterstores():
-            connection = store._connection
-            if connection._state == storm.database.STATE_CONNECTED:
-                if connection._raw_connection is not None:
-                    connection._raw_connection.close()
-
-                # This method assumes that calling transaction.abort() will
-                # call rollback() on the store, but this is no longer the
-                # case as of jamesh's fix for bug 230977; Stores are not
-                # registered with the transaction manager until they are
-                # used. While storm doesn't provide an API which does what
-                # we want, we'll go under the covers and emit the
-                # register-transaction event ourselves. This method is
-                # only called by the test suite to kill the existing
-                # connections so the Store's reconnect with updated
-                # connection settings.
-                store._event.emit('register-transaction')
-
-                connection._raw_connection = None
-                connection._state = storm.database.STATE_DISCONNECTED
-        transaction.abort()
-
-    @classmethod
-    def uninstall(cls):
-        """Uninstall the ZopelessTransactionManager.
-
-        This entails removing the config overlay and resetting the store.
-        """
-        assert cls._installed is not None, (
-            "ZopelessTransactionManager not installed")
-        dbconfig.override(dbuser=None, isolation_level=None)
-        cls._reset_stores()
-        cls._installed = None
-
-
 def clear_current_connection_cache():
     """Clear SQLObject's object cache. SQLObject compatibility - DEPRECATED.
     """

=== removed file 'lib/canonical/database/tests/test_zopeless_transaction_manager.py'
--- lib/canonical/database/tests/test_zopeless_transaction_manager.py	2011-09-20 11:58:21 +0000
+++ lib/canonical/database/tests/test_zopeless_transaction_manager.py	1970-01-01 00:00:00 +0000
@@ -1,24 +0,0 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-from storm.zope.interfaces import IZStorm
-from zope.component import getUtility
-
-from canonical.database.sqlbase import ZopelessTransactionManager
-from canonical.testing.layers import LaunchpadZopelessLayer
-from lp.testing import TestCase
-
-
-class TestZopelessTransactionManager(TestCase):
-    layer = LaunchpadZopelessLayer
-
-    def test_reset_stores_only_does_so_on_active_stores(self):
-        active_stores = [item[0] for item in getUtility(IZStorm).iterstores()]
-        self.assertContentEqual(
-            ['main-master', 'session'], active_stores)
-        ZopelessTransactionManager._reset_stores()
-        # If any other stores had been reset, they'd be activated and would
-        # then be returned by ZStorm.iterstores().
-        new_active_stores = [
-            item[0] for item in getUtility(IZStorm).iterstores()]
-        self.assertContentEqual(active_stores, new_active_stores)

=== modified file 'lib/canonical/testing/layers.py'
--- lib/canonical/testing/layers.py	2011-09-26 00:50:29 +0000
+++ lib/canonical/testing/layers.py	2011-09-26 00:50:29 +0000
@@ -107,10 +107,7 @@
     ConfigFixture,
     ConfigUseFixture,
     )
-from canonical.database.sqlbase import (
-    session_store,
-    ZopelessTransactionManager,
-    )
+from canonical.database.sqlbase import session_store
 from canonical.launchpad.scripts import execute_zcml_for_scripts
 from canonical.launchpad.webapp.interfaces import (
     DEFAULT_FLAVOR,
@@ -151,6 +148,7 @@
     login,
     logout,
     )
+from lp.testing.dbuser import switch_dbuser
 from lp.testing.pgsql import PgTestSetup
 
 
@@ -487,13 +485,6 @@
                 "Component architecture should not be loaded by tests. "
                 "This should only be loaded by the Layer.")
 
-        # Detect a test that installed the Zopeless database adapter
-        # but failed to unregister it. This could be done automatically,
-        # but it is better for the tear down to be explicit.
-        if ZopelessTransactionManager._installed is not None:
-            raise LayerIsolationError(
-                "Zopeless environment was setup and not torn down.")
-
         # Detect a test that forgot to reset the default socket timeout.
         # This safety belt is cheap and protects us from very nasty
         # intermittent test failures: see bug #140068 for an example.
@@ -1532,10 +1523,7 @@
     @classmethod
     @profiled
     def testSetUp(cls):
-        if ZopelessTransactionManager._installed is not None:
-            raise LayerIsolationError(
-                "Last test using Zopeless failed to tearDown correctly")
-        ZopelessTransactionManager.initZopeless(dbuser='launchpad_main')
+        dbconfig.override(isolation_level='read_committed')
         # XXX wgrant 2011-09-24 bug=29744: initZopeless used to do this.
         # Tests that still need it should eventually set this directly,
         # so the whole layer is not polluted.
@@ -1547,10 +1535,7 @@
     @classmethod
     @profiled
     def testTearDown(cls):
-        ZopelessTransactionManager.uninstall()
-        if ZopelessTransactionManager._installed is not None:
-            raise LayerInvariantError(
-                "Failed to uninstall ZopelessTransactionManager")
+        dbconfig.reset()
         # LaunchpadScriptLayer will disconnect the stores for us.
 
         # XXX wgrant 2011-09-24 bug=29744: uninstall used to do this.
@@ -1571,16 +1556,8 @@
     @classmethod
     @profiled
     def switchDbUser(cls, dbuser):
-        LaunchpadZopelessLayer._alterConnection(dbuser=dbuser)
-
-    @classmethod
-    @profiled
-    def _alterConnection(cls, **kw):
-        """Reset the connection, and reopen the connection by calling
-        initZopeless with the given keyword arguments.
-        """
-        ZopelessTransactionManager.uninstall()
-        ZopelessTransactionManager.initZopeless(**kw)
+        # DEPRECATED: use switch_dbuser directly.
+        switch_dbuser(dbuser)
 
 
 class ExperimentalLaunchpadZopelessLayer(LaunchpadZopelessLayer):

=== modified file 'lib/lp/answers/tests/emailinterface.txt'
--- lib/lp/answers/tests/emailinterface.txt	2011-08-12 14:35:12 +0000
+++ lib/lp/answers/tests/emailinterface.txt	2011-09-26 00:50:29 +0000
@@ -108,8 +108,8 @@
     >>> no_priv = personset.getByEmail('no-priv@xxxxxxxxxxxxx')
     >>> foo_bar = personset.getByEmail('foo.bar@xxxxxxxxxxxxx')
 
+    >>> import transaction
     >>> from canonical.config import config
-    >>> from canonical.database.sqlbase import commit
     >>> from canonical.testing.layers import LaunchpadZopelessLayer
 
     >>> LaunchpadZopelessLayer.switchDbUser('launchpad')
@@ -119,7 +119,7 @@
     ...     "I've tried installing Ubuntu on a Mac. But the installer never "
     ...     "boots.", datecreated=now.next())
     >>> question_id = question.id
-    >>> commit()
+    >>> transaction.commit()
     >>> LaunchpadZopelessLayer.switchDbUser(config.processmail.dbuser)
 
     # We need to refetch the question, since a new transaction was started.
@@ -363,6 +363,7 @@
     True
     >>> print message.action.title
     Comment
+    >>> transaction.abort()
 
 
 Answers linked to FAQ questions
@@ -378,7 +379,7 @@
     ...     no_priv, 'Why everyone think this is weird.',
     ...     "That's an easy one. It's because it is!")
     >>> removeSecurityProxy(question).faq = faq
-    >>> commit()
+    >>> transaction.commit()
 
     >>> LaunchpadZopelessLayer.switchDbUser(config.processmail.dbuser)
     >>> login('no-priv@xxxxxxxxxxxxx')

=== modified file 'lib/lp/bugs/tests/externalbugtracker.py'
--- lib/lp/bugs/tests/externalbugtracker.py	2011-06-09 17:12:16 +0000
+++ lib/lp/bugs/tests/externalbugtracker.py	2011-09-26 00:50:29 +0000
@@ -27,10 +27,7 @@
 from zope.component import getUtility
 
 from canonical.config import config
-from canonical.database.sqlbase import (
-    commit,
-    ZopelessTransactionManager,
-    )
+from canonical.database.sqlbase import commit
 from canonical.launchpad.ftests import (
     login,
     logout,
@@ -82,8 +79,6 @@
     closed. After returning from this function, a new connection using
     the checkwatches db user is created.
     """
-    assert ZopelessTransactionManager._installed is not None, (
-        "This function can only be used for Zopeless tests.")
     LaunchpadZopelessLayer.switchDbUser('launchpad')
     owner = getUtility(IPersonSet).getByEmail('no-priv@xxxxxxxxxxxxx')
     bugtracker_set = getUtility(IBugTrackerSet)

=== modified file 'lib/lp/codehosting/scanner/tests/test_bzrsync.py'
--- lib/lp/codehosting/scanner/tests/test_bzrsync.py	2011-09-19 02:23:59 +0000
+++ lib/lp/codehosting/scanner/tests/test_bzrsync.py	2011-09-26 00:50:29 +0000
@@ -24,7 +24,6 @@
 from zope.security.proxy import removeSecurityProxy
 
 from canonical.config import config
-from canonical.database.sqlbase import ZopelessTransactionManager
 from canonical.launchpad.interfaces.lpstorm import IStore
 from canonical.testing.layers import LaunchpadZopelessLayer
 from lp.code.interfaces.branchjob import IRosettaUploadJobSource
@@ -54,6 +53,7 @@
     temp_dir,
     TestCaseWithFactory,
     )
+from lp.testing.dbuser import dbuser
 from lp.translations.interfaces.translations import (
     TranslationsBranchImportMode,
     )
@@ -66,14 +66,8 @@
     def _run_with_different_user(f):
 
         def decorated(*args, **kwargs):
-            current_user = ZopelessTransactionManager._dbuser
-            if current_user == username:
-                return f(*args, **kwargs)
-            LaunchpadZopelessLayer.switchDbUser(username)
-            try:
-                return f(*args, **kwargs)
-            finally:
-                LaunchpadZopelessLayer.switchDbUser(current_user)
+            with dbuser(username):
+                return f(*args, **kwargs)
         return mergeFunctionMetadata(f, decorated)
 
     return _run_with_different_user

=== modified file 'lib/lp/hardwaredb/doc/hwdb-device-tables.txt'
--- lib/lp/hardwaredb/doc/hwdb-device-tables.txt	2010-10-19 18:44:31 +0000
+++ lib/lp/hardwaredb/doc/hwdb-device-tables.txt	2011-09-26 00:50:29 +0000
@@ -1110,6 +1110,8 @@
     >>> sata_controller.getSubmissions(owner=not_owner).count()
     0
 
+    >>> import transaction
+    >>> transaction.abort()
     >>> LaunchpadZopelessLayer.switchDbUser('hwdb-submission-processor')
 
 HWDevice.drivers is the set of drivers that are associated via
@@ -1410,6 +1412,7 @@
     >>> print driver
     None
 
+    >>> transaction.abort()
     >>> LaunchpadZopelessLayer.switchDbUser('launchpad')
 
 We can search all the submissions related to a driver.
@@ -1458,6 +1461,7 @@
     >>> driver.getSubmissions(owner=not_owner).count()
     0
 
+    >>> transaction.abort()
     >>> LaunchpadZopelessLayer.switchDbUser('hwdb-submission-processor')
 
 

=== modified file 'lib/lp/services/job/runner.py'
--- lib/lp/services/job/runner.py	2011-09-26 00:50:29 +0000
+++ lib/lp/services/job/runner.py	2011-09-26 00:50:29 +0000
@@ -54,8 +54,10 @@
 from zope.component import getUtility
 from zope.security.proxy import removeSecurityProxy
 
-from canonical.config import config
-from canonical.database.sqlbase import ZopelessTransactionManager
+from canonical.config import (
+    config,
+    dbconfig,
+    )
 from canonical.launchpad import scripts
 from canonical.launchpad.webapp import errorlog
 from lp.services.job.interfaces.job import (
@@ -372,7 +374,7 @@
             raise TimeoutError
         scripts.execute_zcml_for_scripts(use_web_security=False)
         signal(SIGHUP, handler)
-        ZopelessTransactionManager.initZopeless(dbuser=cls.dbuser)
+        dbconfig.override(dbuser=cls.dbuser, isolation_level='read_committed')
         # XXX wgrant 2011-09-24 bug=29744: initZopeless used to do this.
         # Should be removed from callsites verified to not need it.
         set_immediate_mail_delivery(True)

=== modified file 'lib/lp/services/scripts/base.py'
--- lib/lp/services/scripts/base.py	2011-09-26 00:50:29 +0000
+++ lib/lp/services/scripts/base.py	2011-09-26 00:50:29 +0000
@@ -35,10 +35,6 @@
 
 from canonical.config import config, dbconfig
 from canonical.database.postgresql import ConnectionString
-from canonical.database.sqlbase import (
-    ISOLATION_LEVEL_DEFAULT,
-    ZopelessTransactionManager,
-    )
 from canonical.launchpad import scripts
 from canonical.launchpad.scripts.logger import OopsHandler
 from canonical.launchpad.webapp.errorlog import globalErrorUtility
@@ -319,7 +315,7 @@
         """Actually run the script, executing zcml and initZopeless."""
 
         if isolation is None:
-            isolation = ISOLATION_LEVEL_DEFAULT
+            isolation = 'read_committed'
         self._init_zca(use_web_security=use_web_security)
         self._init_db(isolation=isolation)
 
@@ -365,8 +361,7 @@
         if dbuser is None:
             connstr = ConnectionString(dbconfig.main_master)
             dbuser = connstr.user or dbconfig.dbuser
-        ZopelessTransactionManager.initZopeless(
-            dbuser=dbuser, isolation=isolation)
+        dbconfig.override(dbuser=dbuser, isolation_level=isolation)
         self.txn = transaction
 
     def record_activity(self, date_started, date_completed):
@@ -378,7 +373,7 @@
     @log_unhandled_exception_and_exit
     def lock_and_run(self, blocking=False, skip_delete=False,
                      use_web_security=False,
-                     isolation=ISOLATION_LEVEL_DEFAULT):
+                     isolation='read_committed'):
         """Call lock_or_die(), and then run() the script.
 
         Will die with sys.exit(1) if the locking call fails.

=== modified file 'lib/lp/testing/dbuser.py'
--- lib/lp/testing/dbuser.py	2011-09-19 02:23:59 +0000
+++ lib/lp/testing/dbuser.py	2011-09-26 00:50:29 +0000
@@ -3,18 +3,68 @@
 
 """Provides a context manager to run parts of a test as a different dbuser."""
 
+from __future__ import absolute_import
+
 __metaclass__ = type
 __all__ = [
     'dbuser',
     'lp_dbuser',
+    'switch_dbuser',
     ]
 
 from contextlib import contextmanager
 
+from storm.database import (
+    STATE_CONNECTED,
+    STATE_DISCONNECTED,
+    )
+from storm.zope.interfaces import IZStorm
 import transaction
-
-from canonical.database.sqlbase import ZopelessTransactionManager
-from canonical.testing.layers import LaunchpadZopelessLayer
+from zope.component import getUtility
+
+from canonical.config import dbconfig
+
+
+def update_store_connections():
+    """Update the connection settings for all active stores.
+
+    This is required for connection setting changes to be made visible.
+
+    Unlike disconnect_stores and reconnect_stores, this changes the
+    underlying connection of *existing* stores, leaving existing objects
+    functional.
+    """
+    for name, store in getUtility(IZStorm).iterstores():
+        connection = store._connection
+        if connection._state == STATE_CONNECTED:
+            if connection._raw_connection is not None:
+                connection._raw_connection.close()
+
+            # This method assumes that calling transaction.abort() will
+            # call rollback() on the store, but this is no longer the
+            # case as of jamesh's fix for bug 230977; Stores are not
+            # registered with the transaction manager until they are
+            # used. While storm doesn't provide an API which does what
+            # we want, we'll go under the covers and emit the
+            # register-transaction event ourselves. This method is
+            # only called by the test suite to kill the existing
+            # connections so the Store's reconnect with updated
+            # connection settings.
+            store._event.emit('register-transaction')
+
+            connection._raw_connection = None
+            connection._state = STATE_DISCONNECTED
+    transaction.abort()
+
+
+def switch_dbuser(new_name):
+    """Change the current database user.
+
+    If new_name is None, the default will be restored.
+    """
+    transaction.commit()
+    dbconfig.override(dbuser=new_name)
+    update_store_connections()
 
 
 @contextmanager
@@ -26,14 +76,10 @@
     temporary_name is the name of the dbuser that should be in place for the
     code in the "with" block.
     """
-    restore_name = ZopelessTransactionManager._dbuser
-    transaction.commit()
-    # Note that this will raise an assertion error if the
-    # LaunchpadZopelessLayer is not already set up.
-    LaunchpadZopelessLayer.switchDbUser(temporary_name)
+    old_name = getattr(dbconfig.overrides, 'dbuser', None)
+    switch_dbuser(temporary_name)
     yield
-    transaction.commit()
-    LaunchpadZopelessLayer.switchDbUser(restore_name)
+    switch_dbuser(old_name)
 
 
 def lp_dbuser():

=== modified file 'scripts/branch-rewrite.py'
--- scripts/branch-rewrite.py	2011-09-05 15:42:27 +0000
+++ scripts/branch-rewrite.py	2011-09-26 00:50:29 +0000
@@ -17,7 +17,6 @@
 import os
 import sys
 
-from canonical.database.sqlbase import ISOLATION_LEVEL_AUTOCOMMIT
 from canonical.config import config
 from canonical.launchpad.interfaces.lpstorm import ISlaveStore
 from lp.code.model.branch import Branch
@@ -79,4 +78,4 @@
 
 if __name__ == '__main__':
     BranchRewriteScript("branch-rewrite", dbuser='branch-rewrite').run(
-        isolation=ISOLATION_LEVEL_AUTOCOMMIT)
+        isolation='autocommit')

=== modified file 'scripts/process-accepted.py'
--- scripts/process-accepted.py	2011-09-22 09:47:13 +0000
+++ scripts/process-accepted.py	2011-09-26 00:50:29 +0000
@@ -15,11 +15,10 @@
 
 import _pythonpath
 
-from canonical.database.sqlbase import ISOLATION_LEVEL_READ_COMMITTED
 from lp.soyuz.scripts.processaccepted import ProcessAccepted
 
 
 if __name__ == '__main__':
     script = ProcessAccepted(
         "process-accepted", dbuser='process_accepted')
-    script.lock_and_run(isolation=ISOLATION_LEVEL_READ_COMMITTED)
+    script.lock_and_run()