Merge lp:~stub/launchpad/disco into lp:launchpad

Proposed by Stuart Bishop
Status: Merged
Approved by: Stuart Bishop
Approved revision: no longer in the source branch.
Merged at revision: 13875
Proposed branch: lp:~stub/launchpad/disco
Merge into: lp:launchpad
Diff against target: 603 lines (+333/-22)
12 files modified
configs/testrunner/launchpad-lazr.conf (+4/-4)
lib/canonical/config/tests/test_database_config.py (+2/-1)
lib/canonical/launchpad/doc/canonical-config.txt (+2/-1)
lib/lp/codehosting/tests/test_rewrite.py (+89/-7)
lib/lp/testing/__init__.py (+24/-0)
lib/lp/testing/fixture.py (+71/-1)
lib/lp/testing/pgsql.py (+7/-4)
lib/lp/testing/tests/test_fixture.py (+117/-1)
lib/lp/testing/tests/test_pgsql.py (+1/-1)
scripts/branch-rewrite.py (+14/-2)
setup.py (+1/-0)
versions.cfg (+1/-0)
To merge this branch: bzr merge lp:~stub/launchpad/disco
Reviewer Review Type Date Requested Status
Stuart Bishop (community) Approve
Review via email: mp+74116@code.launchpad.net

Description of the change

= Summary =

Bump pgbouncer release to a version that should pass buildbot and revert r13865 reversion.

== Proposed fix ==

== Pre-implementation notes ==

== Implementation details ==

== Tests ==

== Demo and Q/A ==

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/testing/pgsql.py
  lib/canonical/launchpad/doc/canonical-config.txt
  lib/lp/testing/tests/test_fixture.py
  versions.cfg
  setup.py
  lib/lp/testing/__init__.py
  lib/lp/testing/tests/test_pgsql.py
  lib/lp/testing/fixture.py
  lib/lp/codehosting/tests/test_rewrite.py
  scripts/branch-rewrite.py
  configs/testrunner/launchpad-lazr.conf
  lib/canonical/config/tests/test_database_config.py

./lib/lp/testing/pgsql.py
      90: Line exceeds 78 characters.
     270: Line exceeds 78 characters.
     281: Line exceeds 78 characters.
     282: Line exceeds 78 characters.
     346: Line exceeds 78 characters.
     350: Line exceeds 78 characters.
      47: E261 at least two spaces before inline comment
      90: E501 line too long (80 characters)
     133: E302 expected 2 blank lines, found 0
     136: E302 expected 2 blank lines, found 1
     142: E302 expected 2 blank lines, found 1
     151: E261 at least two spaces before inline comment
     329: E225 missing whitespace around operator
     341: E225 missing whitespace around operator
     346: E501 line too long (88 characters)
     350: E501 line too long (83 characters)
     351: E225 missing whitespace around operator
     365: E261 at least two spaces before inline comment
./lib/canonical/launchpad/doc/canonical-config.txt
       1: narrative uses a moin header.
       9: narrative uses a moin header.
      85: narrative uses a moin header.
     105: narrative uses a moin header.
./lib/lp/testing/tests/test_pgsql.py
     105: E261 at least two spaces before inline comment
     180: E231 missing whitespace after ','
./scripts/branch-rewrite.py
      15: '_pythonpath' imported but unused
./configs/testrunner/launchpad-lazr.conf
     140: Line exceeds 78 characters.

To post a comment you must log in.
Revision history for this message
Stuart Bishop (stub) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'configs/testrunner/launchpad-lazr.conf'
2--- configs/testrunner/launchpad-lazr.conf 2011-09-05 06:30:08 +0000
3+++ configs/testrunner/launchpad-lazr.conf 2011-09-05 16:02:25 +0000
4@@ -45,12 +45,12 @@
5 error_dir: /var/tmp/codehosting.test
6
7 [database]
8-rw_main_master: dbname=launchpad_ftest
9-rw_main_slave: dbname=launchpad_ftest
10+rw_main_master: dbname=launchpad_ftest host=localhost
11+rw_main_slave: dbname=launchpad_ftest host=localhost
12 # Use our _template databases here just so that we have different values from
13 # the rw_* configs.
14-ro_main_master: dbname=launchpad_ftest_template
15-ro_main_slave: dbname=launchpad_ftest_template
16+ro_main_master: dbname=launchpad_ftest_template host=localhost
17+ro_main_slave: dbname=launchpad_ftest_template host=localhost
18 randomise_select_results: true
19
20 [error_reports]
21
22=== modified file 'lib/canonical/config/tests/test_database_config.py'
23--- lib/canonical/config/tests/test_database_config.py 2011-09-05 06:30:08 +0000
24+++ lib/canonical/config/tests/test_database_config.py 2011-09-05 16:02:25 +0000
25@@ -28,7 +28,8 @@
26 self.assertEquals('librarian', config.librarian.dbuser)
27
28 dbconfig.setConfigSection('librarian')
29- expected_db = 'dbname=%s' % DatabaseLayer._db_fixture.dbname
30+ expected_db = (
31+ 'dbname=%s host=localhost' % DatabaseLayer._db_fixture.dbname)
32 self.assertEquals(expected_db, dbconfig.rw_main_master)
33 self.assertEquals('librarian', dbconfig.dbuser)
34
35
36=== modified file 'lib/canonical/launchpad/doc/canonical-config.txt'
37--- lib/canonical/launchpad/doc/canonical-config.txt 2011-09-05 06:30:08 +0000
38+++ lib/canonical/launchpad/doc/canonical-config.txt 2011-09-05 16:02:25 +0000
39@@ -15,7 +15,8 @@
40
41 >>> from canonical.config import config
42 >>> from canonical.testing.layers import DatabaseLayer
43- >>> expected = 'dbname=%s' % DatabaseLayer._db_fixture.dbname
44+ >>> expected = (
45+ ... 'dbname=%s host=localhost' % DatabaseLayer._db_fixture.dbname)
46 >>> expected == config.database.rw_main_master
47 True
48 >>> config.database.db_statement_timeout is None
49
50=== modified file 'lib/lp/codehosting/tests/test_rewrite.py'
51--- lib/lp/codehosting/tests/test_rewrite.py 2011-09-05 06:30:08 +0000
52+++ lib/lp/codehosting/tests/test_rewrite.py 2011-09-05 16:02:25 +0000
53@@ -1,4 +1,4 @@
54-# Copyright 2009 Canonical Ltd. This software is licensed under the
55+# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
56 # GNU Affero General Public License version 3 (see the file LICENSE).
57
58 """Tests for the dynamic RewriteMap used to serve branches over HTTP."""
59@@ -14,15 +14,21 @@
60 from zope.security.proxy import removeSecurityProxy
61
62 from canonical.config import config
63-from canonical.testing.layers import DatabaseFunctionalLayer
64+from canonical.testing.layers import (
65+ DatabaseFunctionalLayer,
66+ DatabaseLayer,
67+ )
68 from lp.code.interfaces.codehosting import branch_id_alias
69 from lp.codehosting.rewrite import BranchRewriter
70 from lp.codehosting.vfs import branch_id_to_path
71 from lp.services.log.logger import BufferLogger
72 from lp.testing import (
73 FakeTime,
74+ nonblocking_readline,
75+ TestCase,
76 TestCaseWithFactory,
77 )
78+from lp.testing.fixture import PGBouncerFixture
79
80
81 class TestBranchRewriter(TestCaseWithFactory):
82@@ -177,7 +183,8 @@
83 transaction.commit()
84 rewriter.rewriteLine('/' + branch.unique_name + '/.bzr/README')
85 rewriter.rewriteLine('/' + branch.unique_name + '/.bzr/README')
86- logging_output_lines = self.getLoggerOutput(rewriter).strip().split('\n')
87+ logging_output_lines = self.getLoggerOutput(
88+ rewriter).strip().split('\n')
89 self.assertEqual(2, len(logging_output_lines))
90 self.assertIsNot(
91 None,
92@@ -194,7 +201,8 @@
93 self.fake_time.advance(
94 config.codehosting.branch_rewrite_cache_lifetime + 1)
95 rewriter.rewriteLine('/' + branch.unique_name + '/.bzr/README')
96- logging_output_lines = self.getLoggerOutput(rewriter).strip().split('\n')
97+ logging_output_lines = self.getLoggerOutput(
98+ rewriter).strip().split('\n')
99 self.assertEqual(2, len(logging_output_lines))
100 self.assertIsNot(
101 None,
102@@ -246,7 +254,8 @@
103 # buffering, write a complete line of output.
104 for input_line in input_lines:
105 proc.stdin.write(input_line + '\n')
106- output_lines.append(proc.stdout.readline().rstrip('\n'))
107+ output_lines.append(
108+ nonblocking_readline(proc.stdout, 60).rstrip('\n'))
109 # If we create a new branch after the branch-rewrite.py script has
110 # connected to the database, or edit a branch name that has already
111 # been rewritten, both are rewritten successfully.
112@@ -260,17 +269,90 @@
113 'file:///var/tmp/bazaar.launchpad.dev/mirrors/%s/.bzr/README'
114 % branch_id_to_path(new_branch.id))
115 proc.stdin.write(new_branch_input + '\n')
116- output_lines.append(proc.stdout.readline().rstrip('\n'))
117+ output_lines.append(
118+ nonblocking_readline(proc.stdout, 60).rstrip('\n'))
119
120 edited_branch_input = '/%s/.bzr/README' % edited_branch.unique_name
121 expected_lines.append(
122 'file:///var/tmp/bazaar.launchpad.dev/mirrors/%s/.bzr/README'
123 % branch_id_to_path(edited_branch.id))
124 proc.stdin.write(edited_branch_input + '\n')
125- output_lines.append(proc.stdout.readline().rstrip('\n'))
126+ output_lines.append(
127+ nonblocking_readline(proc.stdout, 60).rstrip('\n'))
128
129 os.kill(proc.pid, signal.SIGINT)
130 err = proc.stderr.read()
131 # The script produces logging output, but not to stderr.
132 self.assertEqual('', err)
133 self.assertEqual(expected_lines, output_lines)
134+
135+
136+class TestBranchRewriterScriptHandlesDisconnects(TestCase):
137+ """Ensure branch-rewrite.py survives fastdowntime deploys."""
138+ layer = DatabaseLayer
139+
140+ def spawn(self):
141+ script_file = os.path.join(
142+ config.root, 'scripts', 'branch-rewrite.py')
143+
144+ self.rewriter_proc = subprocess.Popen(
145+ [script_file], stdin=subprocess.PIPE, stdout=subprocess.PIPE,
146+ stderr=subprocess.PIPE, bufsize=0)
147+
148+ self.addCleanup(self.rewriter_proc.terminate)
149+
150+ def request(self, query):
151+ self.rewriter_proc.stdin.write(query + '\n')
152+ self.rewriter_proc.stdin.flush()
153+
154+ # 60 second timeout as we might need to wait for the script to
155+ # finish starting up.
156+ result = nonblocking_readline(self.rewriter_proc.stdout, 60)
157+
158+ if result.endswith('\n'):
159+ return result[:-1]
160+ self.fail(
161+ "Incomplete line or no result retrieved from subprocess: %s"
162+ % repr(result.getvalue()))
163+
164+ def test_reconnects_when_disconnected(self):
165+ pgbouncer = self.useFixture(PGBouncerFixture())
166+
167+ self.spawn()
168+
169+ # Everything should be working, and we get valid output.
170+ out = self.request('foo')
171+ self.assertEndsWith(out, '/foo')
172+
173+ pgbouncer.stop()
174+
175+ # Now with pgbouncer down, we should get NULL messages and
176+ # stderr spam, and this keeps happening. We test more than
177+ # once to ensure that we will keep trying to reconnect even
178+ # after several failures.
179+ for count in range(5):
180+ out = self.request('foo')
181+ self.assertEqual(out, 'NULL')
182+
183+ pgbouncer.start()
184+
185+ # Everything should be working, and we get valid output.
186+ out = self.request('foo')
187+ self.assertEndsWith(out, '/foo')
188+
189+ def test_starts_with_db_down(self):
190+ pgbouncer = self.useFixture(PGBouncerFixture())
191+
192+ # Start with the database down.
193+ pgbouncer.stop()
194+
195+ self.spawn()
196+
197+ for count in range(5):
198+ out = self.request('foo')
199+ self.assertEqual(out, 'NULL')
200+
201+ pgbouncer.start()
202+
203+ out = self.request('foo')
204+ self.assertEndsWith(out, '/foo')
205
206=== modified file 'lib/lp/testing/__init__.py'
207--- lib/lp/testing/__init__.py 2011-09-05 06:30:08 +0000
208+++ lib/lp/testing/__init__.py 2011-09-05 16:02:25 +0000
209@@ -29,6 +29,7 @@
210 'logout',
211 'map_branch_contents',
212 'normalize_whitespace',
213+ 'nonblocking_readline',
214 'oauth_access_token_for',
215 'person_logged_in',
216 'quote_jquery_expression',
217@@ -69,6 +70,7 @@
218 import os
219 from pprint import pformat
220 import re
221+from select import select
222 import shutil
223 import subprocess
224 import sys
225@@ -1325,3 +1327,25 @@
226 def extract_lp_cache(text):
227 match = re.search(r'<script>LP.cache = (\{.*\});</script>', text)
228 return simplejson.loads(match.group(1))
229+
230+
231+def nonblocking_readline(instream, timeout):
232+ """Non-blocking readline.
233+
234+ Files must provide a valid fileno() method. This is a test helper
235+ as it is inefficient and unlikely useful for production code.
236+ """
237+ result = StringIO()
238+ start = now = time.time()
239+ deadline = start + timeout
240+ while (now < deadline and not result.getvalue().endswith('\n')):
241+ rlist = select([instream], [], [], deadline - now)
242+ if rlist:
243+ # Reading 1 character at a time is inefficient, but means
244+ # we don't need to implement put-back.
245+ next_char = os.read(instream.fileno(), 1)
246+ if next_char == "":
247+ break # EOF
248+ result.write(next_char)
249+ now = time.time()
250+ return result.getvalue()
251
252=== modified file 'lib/lp/testing/fixture.py'
253--- lib/lp/testing/fixture.py 2011-09-05 06:30:08 +0000
254+++ lib/lp/testing/fixture.py 2011-09-05 16:02:25 +0000
255@@ -11,9 +11,15 @@
256 'ZopeViewReplacementFixture',
257 ]
258
259+from ConfigParser import SafeConfigParser
260+import os.path
261 from textwrap import dedent
262
263-from fixtures import Fixture
264+from fixtures import (
265+ EnvironmentVariableFixture,
266+ Fixture,
267+ )
268+import pgbouncer.fixture
269 import rabbitfixture.server
270 from zope.component import (
271 getGlobalSiteManager,
272@@ -27,6 +33,8 @@
273 undefineChecker,
274 )
275
276+from canonical.config import config
277+
278
279 class RabbitServer(rabbitfixture.server.RabbitServer):
280 """A RabbitMQ server fixture with Launchpad-specific config.
281@@ -46,6 +54,68 @@
282 """ % self.config.port)
283
284
285+class PGBouncerFixture(pgbouncer.fixture.PGBouncerFixture):
286+ """Inserts a controllable pgbouncer instance in front of PostgreSQL.
287+
288+ The pgbouncer proxy can be shutdown and restarted at will, simulating
289+ database outages and fastdowntime deployments.
290+ """
291+
292+ def __init__(self):
293+ super(PGBouncerFixture, self).__init__()
294+
295+ # Known databases
296+ from canonical.testing.layers import DatabaseLayer
297+ dbnames = [
298+ DatabaseLayer._db_fixture.dbname,
299+ DatabaseLayer._db_template_fixture.dbname,
300+ 'session_ftest',
301+ 'launchpad_empty',
302+ ]
303+ for dbname in dbnames:
304+ self.databases[dbname] = 'dbname=%s port=5432 host=localhost' % (
305+ dbname,)
306+
307+ # Known users, pulled from security.cfg
308+ security_cfg_path = os.path.join(
309+ config.root, 'database', 'schema', 'security.cfg')
310+ security_cfg_config = SafeConfigParser({})
311+ security_cfg_config.read([security_cfg_path])
312+ for section_name in security_cfg_config.sections():
313+ self.users[section_name] = 'trusted'
314+ self.users[section_name + '_ro'] = 'trusted'
315+ self.users[os.environ['USER']] = 'trusted'
316+
317+ def setUp(self):
318+ super(PGBouncerFixture, self).setUp()
319+
320+ # reconnect_store cleanup added first so it is run last, after
321+ # the environment variables have been reset.
322+ self.addCleanup(self._maybe_reconnect_stores)
323+
324+ # Abuse the PGPORT environment variable to get things connecting
325+ # via pgbouncer. Otherwise, we would need to temporarily
326+ # overwrite the database connection strings in the config.
327+ self.useFixture(EnvironmentVariableFixture('PGPORT', str(self.port)))
328+
329+ # Reset database connections so they go through pgbouncer.
330+ self._maybe_reconnect_stores()
331+
332+ def _maybe_reconnect_stores(self):
333+ """Force Storm Stores to reconnect if they are registered.
334+
335+ This is a noop if the Component Architecture is not loaded,
336+ as we are using a test layer that doesn't provide database
337+ connections.
338+ """
339+ from canonical.testing.layers import (
340+ reconnect_stores,
341+ is_ca_available,
342+ )
343+ if is_ca_available():
344+ reconnect_stores()
345+
346+
347 class ZopeAdapterFixture(Fixture):
348 """A fixture to register and unregister an adapter."""
349
350
351=== modified file 'lib/lp/testing/pgsql.py'
352--- lib/lp/testing/pgsql.py 2011-09-05 06:30:08 +0000
353+++ lib/lp/testing/pgsql.py 2011-09-05 16:02:25 +0000
354@@ -1,4 +1,4 @@
355-# Copyright 2009 Canonical Ltd. This software is licensed under the
356+# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
357 # GNU Affero General Public License version 3 (see the file LICENSE).
358
359 '''
360@@ -41,7 +41,10 @@
361 def close(self):
362 if self in PgTestSetup.connections:
363 PgTestSetup.connections.remove(self)
364- self.real_connection.close()
365+ try:
366+ self.real_connection.close()
367+ except psycopg2.InterfaceError:
368+ pass # Already closed, killed etc. Ignore.
369
370 def rollback(self, InterfaceError=psycopg2.InterfaceError):
371 # In our test suites, rollback ends up being called twice in some
372@@ -186,8 +189,8 @@
373 # available.
374 # Avoid circular imports
375 section = """[database]
376-rw_main_master: dbname=%s
377-rw_main_slave: dbname=%s
378+rw_main_master: dbname=%s host=localhost
379+rw_main_slave: dbname=%s host=localhost
380
381 """ % (self.dbname, self.dbname)
382 if BaseLayer.config_fixture is not None:
383
384=== modified file 'lib/lp/testing/tests/test_fixture.py'
385--- lib/lp/testing/tests/test_fixture.py 2011-09-05 06:30:08 +0000
386+++ lib/lp/testing/tests/test_fixture.py 2011-09-05 16:02:25 +0000
387@@ -8,6 +8,8 @@
388 from textwrap import dedent
389
390 from fixtures import EnvironmentVariableFixture
391+import psycopg2
392+from storm.exceptions import DisconnectionError
393 from zope.component import (
394 adapts,
395 queryAdapter,
396@@ -17,9 +19,17 @@
397 Interface,
398 )
399
400-from canonical.testing.layers import BaseLayer
401+from canonical.config import dbconfig
402+from canonical.launchpad.interfaces.lpstorm import IMasterStore
403+from canonical.testing.layers import (
404+ BaseLayer,
405+ DatabaseLayer,
406+ LaunchpadZopelessLayer,
407+ )
408+from lp.registry.model.person import Person
409 from lp.testing import TestCase
410 from lp.testing.fixture import (
411+ PGBouncerFixture,
412 RabbitServer,
413 ZopeAdapterFixture,
414 )
415@@ -89,3 +99,109 @@
416 self.assertIsInstance(adapter, FooToBar)
417 # The adapter is no longer registered.
418 self.assertIs(None, queryAdapter(context, IBar))
419+
420+
421+class TestPGBouncerFixtureWithCA(TestCase):
422+ """PGBouncerFixture reconnect tests for Component Architecture layers.
423+
424+ Registered Storm Stores should be reconnected through pgbouncer.
425+ """
426+ layer = LaunchpadZopelessLayer
427+
428+ def is_connected(self):
429+ # First rollback any existing transaction to ensure we attempt
430+ # to reconnect. We currently rollback the store explicitely
431+ # rather than call transaction.abort() due to Bug #819282.
432+ store = IMasterStore(Person)
433+ store.rollback()
434+
435+ try:
436+ store.find(Person).first()
437+ return True
438+ except DisconnectionError:
439+ return False
440+
441+ def test_stop_and_start(self):
442+ # Database is working.
443+ assert self.is_connected()
444+
445+ # And database with the fixture is working too.
446+ pgbouncer = PGBouncerFixture()
447+ with PGBouncerFixture() as pgbouncer:
448+ assert self.is_connected()
449+
450+ # pgbouncer is transparant. To confirm we are connecting via
451+ # pgbouncer, we need to shut it down and confirm our
452+ # connections are dropped.
453+ pgbouncer.stop()
454+ assert not self.is_connected()
455+
456+ # If we restart it, things should be back to normal.
457+ pgbouncer.start()
458+ assert self.is_connected()
459+
460+ # Database is still working.
461+ assert self.is_connected()
462+
463+ def test_stop_no_start(self):
464+ # Database is working.
465+ assert self.is_connected()
466+
467+ # And database with the fixture is working too.
468+ with PGBouncerFixture() as pgbouncer:
469+ assert self.is_connected()
470+
471+ # pgbouncer is transparant. To confirm we are connecting via
472+ # pgbouncer, we need to shut it down and confirm our
473+ # connections are dropped.
474+ pgbouncer.stop()
475+ assert not self.is_connected()
476+
477+ # Database is working again.
478+ assert self.is_connected()
479+
480+
481+class TestPGBouncerFixtureWithoutCA(TestCase):
482+ """PGBouncerFixture tests for non-Component Architecture layers."""
483+ layer = DatabaseLayer
484+
485+ def is_db_available(self):
486+ # Direct connection to the DB.
487+ con_str = dbconfig.rw_main_master + ' user=launchpad_main'
488+ try:
489+ con = psycopg2.connect(con_str)
490+ cur = con.cursor()
491+ cur.execute("SELECT id FROM Person LIMIT 1")
492+ con.close()
493+ return True
494+ except psycopg2.OperationalError:
495+ return False
496+
497+ def test_install_fixture(self):
498+ self.assert_(self.is_db_available())
499+
500+ with PGBouncerFixture() as pgbouncer:
501+ self.assertTrue(self.is_db_available())
502+
503+ pgbouncer.stop()
504+ self.assertFalse(self.is_db_available())
505+
506+ # This confirms that we are again connecting directly to the
507+ # database, as the pgbouncer process was shutdown.
508+ self.assertTrue(self.is_db_available())
509+
510+ def test_install_fixture_with_restart(self):
511+ self.assert_(self.is_db_available())
512+
513+ with PGBouncerFixture() as pgbouncer:
514+ self.assertTrue(self.is_db_available())
515+
516+ pgbouncer.stop()
517+ self.assertFalse(self.is_db_available())
518+
519+ pgbouncer.start()
520+ self.assertTrue(self.is_db_available())
521+
522+ # Note that because pgbouncer was left running, we can't confirm
523+ # that we are now connecting directly to the database.
524+ self.assertTrue(self.is_db_available())
525
526=== modified file 'lib/lp/testing/tests/test_pgsql.py'
527--- lib/lp/testing/tests/test_pgsql.py 2011-09-05 06:30:08 +0000
528+++ lib/lp/testing/tests/test_pgsql.py 2011-09-05 16:02:25 +0000
529@@ -53,7 +53,7 @@
530 fixture.setUp()
531 self.addCleanup(fixture.dropDb)
532 self.addCleanup(fixture.tearDown)
533- expected_value = 'dbname=%s' % fixture.dbname
534+ expected_value = 'dbname=%s host=localhost' % fixture.dbname
535 self.assertEqual(expected_value, dbconfig.rw_main_master)
536 self.assertEqual(expected_value, dbconfig.rw_main_slave)
537 with ConfigUseFixture(BaseLayer.appserver_config_name):
538
539=== modified file 'scripts/branch-rewrite.py'
540--- scripts/branch-rewrite.py 2011-09-05 06:30:08 +0000
541+++ scripts/branch-rewrite.py 2011-09-05 16:02:25 +0000
542@@ -1,6 +1,6 @@
543 #!/usr/bin/python -uS
544 #
545-# Copyright 2009 Canonical Ltd. This software is licensed under the
546+# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
547 # GNU Affero General Public License version 3 (see the file LICENSE).
548
549 # pylint: disable-msg=W0403
550@@ -19,6 +19,8 @@
551
552 from canonical.database.sqlbase import ISOLATION_LEVEL_AUTOCOMMIT
553 from canonical.config import config
554+from canonical.launchpad.interfaces.lpstorm import ISlaveStore
555+from lp.code.model.branch import Branch
556 from lp.codehosting.rewrite import BranchRewriter
557 from lp.services.log.loglevels import INFO, WARNING
558 from lp.services.scripts.base import LaunchpadScript
559@@ -60,9 +62,19 @@
560 return
561 except KeyboardInterrupt:
562 sys.exit()
563- except:
564+ except Exception:
565 self.logger.exception('Exception occurred:')
566 print "NULL"
567+ # The exception might have been a DisconnectionError or
568+ # similar. Cleanup such as database reconnection will
569+ # not happen until the transaction is rolled back.
570+ # XXX StuartBishop 2011-08-31 bug=819282: We are
571+ # explicitly rolling back the store here as a workaround
572+ # instead of using transaction.abort()
573+ try:
574+ ISlaveStore(Branch).rollback()
575+ except Exception:
576+ self.logger.exception('Exception occurred in rollback:')
577
578
579 if __name__ == '__main__':
580
581=== modified file 'setup.py'
582--- setup.py 2011-09-05 06:30:08 +0000
583+++ setup.py 2011-09-05 16:02:25 +0000
584@@ -60,6 +60,7 @@
585 'oops_datedir_repo',
586 'oops_wsgi',
587 'paramiko',
588+ 'pgbouncer',
589 'psycopg2',
590 'python-memcached',
591 'pyasn1',
592
593=== modified file 'versions.cfg'
594--- versions.cfg 2011-09-05 06:30:08 +0000
595+++ versions.cfg 2011-09-05 16:02:25 +0000
596@@ -55,6 +55,7 @@
597 paramiko = 1.7.4
598 Paste = 1.7.2
599 PasteDeploy = 1.3.3
600+pgbouncer = 0.0.3
601 plone.recipe.command = 1.1
602 psycopg2 = 2.2.2
603 pyasn1 = 0.0.9a