Merge lp:~wgrant/launchpad/connectionstring-hates-production into lp:launchpad

Proposed by William Grant
Status: Merged
Approved by: Ian Booth
Approved revision: no longer in the source branch.
Merged at revision: 13890
Proposed branch: lp:~wgrant/launchpad/connectionstring-hates-production
Merge into: lp:launchpad
Diff against target: 76 lines (+46/-1)
2 files modified
lib/canonical/database/postgresql.py (+6/-1)
lib/canonical/database/tests/test_connectionstring.py (+40/-0)
To merge this branch: bzr merge lp:~wgrant/launchpad/connectionstring-hates-production
Reviewer Review Type Date Requested Status
Ian Booth (community) code Approve
Review via email: mp+74321@code.launchpad.net

Commit message

ConnectionString now assumes values continue until a space character, and rejects values that seem to be quoted or escaped. Also now has better tests.

Description of the change

This branch fixes ConnectionString to not truncate production DB hostnames. See bug #843412 for details of the problem.

As I suggested in the bug, I've not implemented escaping or quoting since we don't need them in production, and they would greatly increase complexity and time to fix. I have, however, added assertions so we crash instead of misparse.

I used gina against the production configs to verify the fix:

$ LPCONFIG=gina scripts/gina.py -vv lenny # before
2011-09-07 00:30:15 DEBUG Cronscript control file not found at file:cronscripts.ini
2011-09-07 00:30:15 INFO Creating lockfile: /var/lock/launchpad-gina.lock
2011-09-07 00:30:21 INFO
2011-09-07 00:30:21 INFO === Processing debian/lenny/release ===
2011-09-07 00:30:21 DEBUG Packages read from: /srv/debian-import.launchpad.net/mirror/
2011-09-07 00:30:21 DEBUG Keyrings read from: /usr/share/keyrings
2011-09-07 00:30:21 INFO Components to import: main, contrib, non-free
2011-09-07 00:30:21 INFO Architectures to import: i386, powerpc, amd64
2011-09-07 00:30:21 DEBUG Launchpad database: launchpad_prod_3
2011-09-07 00:30:21 DEBUG Launchpad database host: lp
2011-09-07 00:30:21 DEBUG Launchpad database user: gina

$ LPCONFIG=gina scripts/gina.py -vv lenny # after
2011-09-07 00:29:36 DEBUG Cronscript control file not found at file:cronscripts.ini
2011-09-07 00:29:36 INFO Creating lockfile: /var/lock/launchpad-gina.lock
2011-09-07 00:29:41 INFO
2011-09-07 00:29:41 INFO === Processing debian/lenny/release ===
2011-09-07 00:29:41 DEBUG Packages read from: /srv/debian-import.launchpad.net/mirror/
2011-09-07 00:29:41 DEBUG Keyrings read from: /usr/share/keyrings
2011-09-07 00:29:41 INFO Components to import: main, contrib, non-free
2011-09-07 00:29:41 INFO Architectures to import: i386, powerpc, amd64
2011-09-07 00:29:41 DEBUG Launchpad database: launchpad_prod_3
2011-09-07 00:29:41 DEBUG Launchpad database host: lp-db-master.canonical.com
2011-09-07 00:29:41 DEBUG Launchpad database user: gina

To post a comment you must log in.
Revision history for this message
Ian Booth (wallyworld) wrote :

Looks good

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/canonical/database/postgresql.py'
--- lib/canonical/database/postgresql.py 2009-09-21 08:13:40 +0000
+++ lib/canonical/database/postgresql.py 2011-09-07 00:46:24 +0000
@@ -482,6 +482,8 @@
482 need the components seperated out (such as pg_dump command line482 need the components seperated out (such as pg_dump command line
483 arguments). This class allows you to switch easily between formats.483 arguments). This class allows you to switch easily between formats.
484484
485 Quoted or escaped values are not supported.
486
485 >>> cs = ConnectionString('user=foo dbname=launchpad_dev')487 >>> cs = ConnectionString('user=foo dbname=launchpad_dev')
486 >>> cs.dbname488 >>> cs.dbname
487 'launchpad_dev'489 'launchpad_dev'
@@ -496,6 +498,9 @@
496 'dbname', 'user', 'host', 'port', 'connect_timeout', 'sslmode']498 'dbname', 'user', 'host', 'port', 'connect_timeout', 'sslmode']
497499
498 def __init__(self, conn_str):500 def __init__(self, conn_str):
501 if "'" in conn_str or "\\" in conn_str:
502 raise AssertionError("quoted or escaped values are not supported")
503
499 if '=' not in conn_str:504 if '=' not in conn_str:
500 # Just a dbname505 # Just a dbname
501 for key in self.CONNECTION_KEYS:506 for key in self.CONNECTION_KEYS:
@@ -507,7 +512,7 @@
507 # be added after construction or not actually required512 # be added after construction or not actually required
508 # at all in some instances.513 # at all in some instances.
509 for key in self.CONNECTION_KEYS:514 for key in self.CONNECTION_KEYS:
510 match = re.search(r'%s=(\w+)' % key, conn_str)515 match = re.search(r'%s=([^ ]+)' % key, conn_str)
511 if match is None:516 if match is None:
512 setattr(self, key, None)517 setattr(self, key, None)
513 else:518 else:
514519
=== added file 'lib/canonical/database/tests/test_connectionstring.py'
--- lib/canonical/database/tests/test_connectionstring.py 1970-01-01 00:00:00 +0000
+++ lib/canonical/database/tests/test_connectionstring.py 2011-09-07 00:46:24 +0000
@@ -0,0 +1,40 @@
1# Copyright 2011 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4from canonical.database.postgresql import ConnectionString
5from lp.testing import TestCase
6
7
8class TestConnectionString(TestCase):
9
10 def test_relevant_fields_parsed(self):
11 s = ('dbname=dbname user=user host=host port=port '
12 'connect_timeout=timeout sslmode=mode')
13 cs = ConnectionString(s)
14 self.assertEqual('dbname', cs.dbname)
15 self.assertEqual('user', cs.user)
16 self.assertEqual('host', cs.host)
17 self.assertEqual('port', cs.port)
18 self.assertEqual('timeout', cs.connect_timeout)
19 self.assertEqual('mode', cs.sslmode)
20
21 # and check that str/repr have the same keys and values.
22 self.assertContentEqual(s.split(), str(cs).split())
23 self.assertContentEqual(s.split(), repr(cs).split())
24
25 def test_hyphens_in_values(self):
26 cs = ConnectionString('user=foo-bar host=foo.bar-baz.quux')
27 self.assertEqual('foo-bar', cs.user)
28 self.assertEqual('foo.bar-baz.quux', cs.host)
29
30 def test_str_with_changes(self):
31 initial = 'dbname=foo host=bar'
32 expected = 'dbname=foo user=baz host=blah'
33 cs = ConnectionString(initial)
34 cs.host = 'blah'
35 cs.user = 'baz'
36 self.assertEqual(expected, str(cs))
37
38 def test_rejects_quoted_strings(self):
39 self.assertRaises(
40 AssertionError, ConnectionString, "dbname='quoted string'")