Merge lp:~bloodearnest/charms/trusty/postgresql/trunk into lp:charms/trusty/postgresql

Proposed by Simon Davy
Status: Merged
Merged at revision: 132
Proposed branch: lp:~bloodearnest/charms/trusty/postgresql/trunk
Merge into: lp:charms/trusty/postgresql
Diff against target: 208 lines (+135/-14)
2 files modified
hooks/service.py (+23/-14)
tests/test_pg_hba_conf.py (+112/-0)
To merge this branch: bzr merge lp:~bloodearnest/charms/trusty/postgresql/trunk
Reviewer Review Type Date Requested Status
Review Queue (community) automated testing Approve
Stuart Bishop (community) Approve
Review via email: mp+278077@code.launchpad.net

Commit message

* Add tests for pg_hba.conf generation
     - found/fixed bug in extra_pg_auth handiling
     - found/fixed 2 bugs in master relation handling
     - found/fixed bug in admin_addresses handling

    * Slight refactor of update_pg_hba_conf to make more testable, moving the main
    logic into a separate function with params, rather than embedding stateful
    functions in amongst the logic

Description of the change

Add test coverage of the pg_hba.conf generation, and fix several bugs in the process.

To post a comment you must log in.
133. By Simon Davy

add test file

Revision history for this message
Review Queue (review-queue) wrote :

The results (PASS) are in and available here: http://juju-ci.vapour.ws:8080/job/charm-bundle-test-lxc/1470/

review: Approve (automated testing)
Revision history for this message
Stuart Bishop (stub) wrote :

I approve of this branch.

review: Approve
Revision history for this message
Review Queue (review-queue) wrote :

The results (PASS) are in and available here: http://juju-ci.vapour.ws:8080/job/charm-bundle-test-aws/1455/

review: Approve (automated testing)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'hooks/service.py'
2--- hooks/service.py 2015-10-01 01:36:42 +0000
3+++ hooks/service.py 2015-11-19 21:24:52 +0000
4@@ -323,6 +323,22 @@
5
6 @data_ready_action
7 def update_pg_hba_conf():
8+
9+ # grab the needed current state
10+ config = hookenv.config()
11+ rels = context.Relations()
12+ path = postgresql.pg_hba_conf_path()
13+ with open(path, 'r') as f:
14+ pg_hba = f.read()
15+
16+ # generate the new state
17+ pg_hba_content = generate_pg_hba_conf(pg_hba, config, rels)
18+
19+ # write out the new state
20+ helpers.rewrite(path, pg_hba_content)
21+
22+
23+def generate_pg_hba_conf(pg_hba, config, rels):
24 '''Update the pg_hba.conf file (host based authentication).'''
25 rules = [] # The ordered list, as tuples.
26
27@@ -344,8 +360,6 @@
28 # user connect to their matching PostgreSQL user, if it exists.
29 add('local', 'all', 'all', 'peer')
30
31- rels = context.Relations()
32-
33 # Peers need replication access as the charm replication user.
34 if rels.peer:
35 for peer, relinfo in rels.peer.items():
36@@ -390,14 +404,14 @@
37 # as the relation gets its own user to avoid sharing credentials,
38 # and logical replication connections will want to specify the
39 # database name.
40- for rel in rels['master']:
41+ for rel in rels['master'].values():
42 for relinfo in rel.values():
43 addr = postgresql.addr_to_range(relinfo['private-address'])
44 add('host', 'replication',
45 postgresql.quote_identifier(rel.local['user']),
46 postgresql.quote_identifier(addr),
47 'md5', '# {}'.format(relinfo))
48- if 'database' is rel.local:
49+ if 'database' in rel.local:
50 add('host',
51 postgresql.quote_identifier(rel.local['database']),
52 postgresql.quote_identifier(rel.local['user']),
53@@ -405,25 +419,20 @@
54 'md5', '# {}'.format(relinfo))
55
56 # External administrative addresses, if specified by the operator.
57- config = hookenv.config()
58 for addr in config['admin_addresses'].split(','):
59 if addr:
60- add('host', 'all', 'all', postgresql.addr_to_range(addr),
61+ add('host', 'all', 'all',
62+ postgresql.quote_identifier(postgresql.addr_to_range(addr)),
63 'md5', '# admin_addresses config')
64
65 # And anything-goes rules, if specified by the operator.
66- for line in config['extra_pg_auth'].splitlines():
67- add((line, '# extra_pg_auth config'))
68+ for line in config['extra_pg_auth'].split(','):
69+ add(line + '# extra_pg_auth config')
70
71 # Deny everything else
72 add('local', 'all', 'all', 'reject', '# Refuse by default')
73 add('host', 'all', 'all', 'all', 'reject', '# Refuse by default')
74
75- # Load the existing file
76- path = postgresql.pg_hba_conf_path()
77- with open(path, 'r') as f:
78- pg_hba = f.read()
79-
80 # Strip out the existing juju managed section
81 start_mark = '### BEGIN JUJU SETTINGS ###'
82 end_mark = '### END JUJU SETTINGS ###'
83@@ -438,7 +447,7 @@
84 rules.insert(0, (start_mark,))
85 rules.append((end_mark,))
86 pg_hba += '\n' + '\n'.join(' '.join(rule) for rule in rules)
87- helpers.rewrite(path, pg_hba)
88+ return pg_hba
89
90
91 def assemble_postgresql_conf():
92
93=== added file 'tests/test_pg_hba_conf.py'
94--- tests/test_pg_hba_conf.py 1970-01-01 00:00:00 +0000
95+++ tests/test_pg_hba_conf.py 2015-11-19 21:24:52 +0000
96@@ -0,0 +1,112 @@
97+# Copyright 2015 Canonical Ltd.
98+#
99+# This file is part of the PostgreSQL Charm for Juju.
100+#
101+# This program is free software: you can redistribute it and/or modify
102+# it under the terms of the GNU General Public License version 3, as
103+# published by the Free Software Foundation.
104+#
105+# This program is distributed in the hope that it will be useful, but
106+# WITHOUT ANY WARRANTY; without even the implied warranties of
107+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
108+# PURPOSE. See the GNU General Public License for more details.
109+#
110+# You should have received a copy of the GNU General Public License
111+# along with this program. If not, see <http://www.gnu.org/licenses/>.
112+import os
113+import sys
114+import unittest
115+
116+sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, 'hooks'))
117+
118+from service import generate_pg_hba_conf
119+from collections import defaultdict
120+
121+
122+# set of classes to stub out the context relation interface
123+class Relations(defaultdict):
124+ def __init__(self, *args, **kwargs):
125+ # set RelationSet as default factory
126+ super(Relations, self).__init__(RelationSet, *args, **kwargs)
127+ self.peer = {}
128+
129+
130+class RelationSet(dict):
131+ def add_unit(self, unit, data=None, local=None):
132+ rel = RelationData()
133+ rel[unit] = {'private-address': '1.2.3.4'}
134+ if local is not None:
135+ rel.local.update(**local)
136+ if data is not None:
137+ rel[unit].update(**data)
138+ self[unit] = rel
139+
140+
141+class RelationData(dict):
142+ def __init__(self, *args, **kwargs):
143+ super(RelationData, self).__init__(*args, **kwargs)
144+ self.local = defaultdict(str)
145+
146+
147+class TestPgHbaConf(unittest.TestCase):
148+
149+ def test_no_relations_or_config(self):
150+ content = generate_pg_hba_conf('', defaultdict(str), Relations())
151+ self.assertIn('local all postgres peer map=juju_charm', content)
152+ self.assertIn('local all all peer', content)
153+ self.assertIn('local all all reject', content)
154+ self.assertIn('host all all all reject', content)
155+
156+ def test_peer_relation(self):
157+ rels = Relations()
158+ rels.peer = {
159+ 'unit/1': {'private-address': '1.2.3.4'},
160+ }
161+ content = generate_pg_hba_conf('', defaultdict(str), rels)
162+ self.assertIn('host replication _juju_repl "1.2.3.4/32" md5', content)
163+ self.assertIn('host postgres _juju_repl "1.2.3.4/32" md5', content)
164+
165+ def test_db_relation(self):
166+ rels = Relations()
167+ rels['db'].add_unit('unit/1', local={
168+ 'user': 'user',
169+ 'database': 'database',
170+ 'schema_user': 'schema_user',
171+ })
172+ content = generate_pg_hba_conf('', defaultdict(str), rels)
173+ self.assertIn('host "database" "user" "1.2.3.4/32" md5', content)
174+ self.assertIn(
175+ 'host "database" "schema_user" "1.2.3.4/32" md5', content)
176+
177+ def test_db_admin_relation(self):
178+ rels = Relations()
179+ rels['db-admin'].add_unit('unit/1', local=({'user': 'user'}))
180+ content = generate_pg_hba_conf('', defaultdict(str), rels)
181+ self.assertIn('host all "user" "1.2.3.4/32" md5', content)
182+
183+ def test_master_relation(self):
184+ rels = Relations()
185+ rels['master'].add_unit('unit/1', local=({
186+ 'user': 'user',
187+ 'database': 'database',
188+ }))
189+ content = generate_pg_hba_conf('', defaultdict(str), rels)
190+ self.assertIn('host replication "user" "1.2.3.4/32" md5', content)
191+ self.assertIn('host "database" "user" "1.2.3.4/32" md5', content)
192+
193+ def test_admin_addresses_config(self):
194+ rels = Relations()
195+ config = defaultdict(str)
196+ config['admin_addresses'] = '192.168.1.0/24,10.0.0.0/8,1.2.3.4'
197+ content = generate_pg_hba_conf('', config, rels)
198+ self.assertIn('host all all "192.168.1.0/24" md5', content)
199+ self.assertIn('host all all "10.0.0.0/8" md5', content)
200+ self.assertIn('host all all "1.2.3.4/32" md5', content)
201+
202+ def test_extra_pg_auth(self):
203+ rels = Relations()
204+ config = defaultdict(str)
205+ config['extra_pg_auth'] = 'local all sso md5,local all ssoadmin md5'
206+ content = generate_pg_hba_conf('', config, rels)
207+ self.assertIn('\nlocal all sso md5', content)
208+ self.assertIn('\nlocal all ssoadmin md5', content)

Subscribers

People subscribed via source and target branches

to all changes: