Merge lp:~stub/charms/precise/postgresql/repoless into lp:charms/postgresql

Proposed by Stuart Bishop
Status: Superseded
Proposed branch: lp:~stub/charms/precise/postgresql/repoless
Merge into: lp:charms/postgresql
Prerequisite: lp:~stub/charms/precise/postgresql/data-checksums
Diff against target: 330 lines (+234/-2) (has conflicts)
7 files modified
lib/test-client-charm/config.yaml (+13/-0)
lib/test-client-charm/hooks/hooks.py (+165/-0)
lib/test-client-charm/hooks/start (+3/-0)
lib/test-client-charm/hooks/stop (+3/-0)
lib/test-client-charm/metadata.yaml (+13/-0)
test.py (+13/-2)
testing/jujufixture.py (+24/-0)
Text conflict in hooks/hooks.py
To merge this branch: bzr merge lp:~stub/charms/precise/postgresql/repoless
Reviewer Review Type Date Requested Status
charmers Pending
Review via email: mp+238256@code.launchpad.net

Commit message

Embed test charms and remove JUJU_REPOSITORY requirement

Description of the change

A common issue with running the PostgreSQL charm is getting JUJU_REPOSITORY setup.

Because the PostgreSQL charm and thus its tests is supported on both trusty and precise, the test suite detects the juju environment's default-series and uses that as the series to use, unless overridden. This in itself isn't a problem, except that the automatic test runner is attempting to run tests with precise, but has a juju environment setup with default-series of trusty, and everything explodes.

Also, the postgresql-psql charm is not in the charm store under trusty, so needed to be in JUJU_REPOSITORY. I elected to not get it pushed to trusty, as its current dual purpose of being a general purpose command line client and for driving tests means it works poorly on both.

While the automatic test runner is getting sorted out, lets do some improvements I'd been planning on anyway. Embed the the client charm our tests need, and build a JUJU_REPOSITORY dynamically.

This still leaves the charm detecting the series to use, so the automatic test runner will continue to actually be running trusty tests when it was hoping for precise, but at least they should pass now. This cannot be fixed until the test environment is setup with a correct default-series, or some other mechanism is available for detecting the series to test against (such as an environment variable).

To post a comment you must log in.
165. By Stuart Bishop

Ignore regenerated charmhelpers

166. By Stuart Bishop

Merged data-checksums into repoless.

Unmerged revisions

166. By Stuart Bishop

Merged data-checksums into repoless.

165. By Stuart Bishop

Ignore regenerated charmhelpers

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'lib/test-client-charm'
2=== added file 'lib/test-client-charm/config.yaml'
3--- lib/test-client-charm/config.yaml 1970-01-01 00:00:00 +0000
4+++ lib/test-client-charm/config.yaml 2014-10-14 08:59:11 +0000
5@@ -0,0 +1,13 @@
6+options:
7+ database:
8+ default: ""
9+ type: string
10+ description: |
11+ Database to connect 'db' relationships to, overriding the
12+ generated default database name.
13+ roles:
14+ default: ""
15+ type: string
16+ description: |
17+ Comma separated list of roles for PostgreSQL to grant to the database
18+ user.
19
20=== added directory 'lib/test-client-charm/hooks'
21=== added symlink 'lib/test-client-charm/hooks/config-changed'
22=== target is u'hooks.py'
23=== added symlink 'lib/test-client-charm/hooks/db-admin-relation-broken'
24=== target is u'./hooks.py'
25=== added symlink 'lib/test-client-charm/hooks/db-admin-relation-changed'
26=== target is u'./hooks.py'
27=== added symlink 'lib/test-client-charm/hooks/db-admin-relation-joined'
28=== target is u'./hooks.py'
29=== added symlink 'lib/test-client-charm/hooks/db-relation-broken'
30=== target is u'./hooks.py'
31=== added symlink 'lib/test-client-charm/hooks/db-relation-changed'
32=== target is u'./hooks.py'
33=== added symlink 'lib/test-client-charm/hooks/db-relation-joined'
34=== target is u'./hooks.py'
35=== added file 'lib/test-client-charm/hooks/hooks.py'
36--- lib/test-client-charm/hooks/hooks.py 1970-01-01 00:00:00 +0000
37+++ lib/test-client-charm/hooks/hooks.py 2014-10-14 08:59:11 +0000
38@@ -0,0 +1,165 @@
39+#!/usr/bin/env python
40+
41+import os.path
42+import re
43+import shutil
44+import sys
45+from textwrap import dedent
46+
47+from charmhelpers.core import hookenv, host
48+from charmhelpers.core.hookenv import log, DEBUG, INFO
49+from charmhelpers import fetch
50+
51+
52+CLIENT_RELATION_TYPES = frozenset(['db', 'db-admin'])
53+
54+DATA_DIR = os.path.join(
55+ '/var/lib/units', hookenv.local_unit().replace('/', '-'))
56+SCRIPT_DIR = os.path.join(DATA_DIR, 'bin')
57+PGPASS_DIR = os.path.join(DATA_DIR, 'pgpass')
58+
59+
60+def update_system_path():
61+ org_lines = open('/etc/environment', 'rb').readlines()
62+ env_lines = []
63+
64+ for line in org_lines:
65+ if line.startswith('PATH=') and SCRIPT_DIR not in line:
66+ line = re.sub(
67+ """(['"]?)$""",
68+ ":{}\\1".format(SCRIPT_DIR),
69+ line, 1)
70+ env_lines.append(line)
71+
72+ if org_lines != env_lines:
73+ content = '\n'.join(env_lines)
74+ host.write_file('/etc/environment', content, perms=0o644)
75+
76+
77+def all_relations(relation_types=CLIENT_RELATION_TYPES):
78+ for reltype in relation_types:
79+ for relid in hookenv.relation_ids(reltype):
80+ for unit in hookenv.related_units(relid):
81+ yield reltype, relid, unit, hookenv.relation_get(
82+ unit=unit, rid=relid)
83+
84+
85+def rebuild_all_relations():
86+ config = hookenv.config()
87+
88+ # Clear out old scripts and pgpass files
89+ if os.path.exists(SCRIPT_DIR):
90+ shutil.rmtree(SCRIPT_DIR)
91+ if os.path.exists(PGPASS_DIR):
92+ shutil.rmtree(PGPASS_DIR)
93+ host.mkdir(DATA_DIR, perms=0o755)
94+ host.mkdir(SCRIPT_DIR, perms=0o755)
95+ host.mkdir(PGPASS_DIR, group='ubuntu', perms=0o750)
96+
97+ for _, relid, unit, relation in all_relations(relation_types=['db']):
98+ log("{} {} {!r}".format(relid, unit, relation), DEBUG)
99+
100+ def_str = '<DEFAULT>'
101+ if config['database'] != relation.get('database', ''):
102+ log("Switching from database {} to {}".format(
103+ relation.get('database', '') or def_str,
104+ config['database'] or def_str), INFO)
105+
106+ if config['roles'] != relation.get('roles', ''):
107+ log("Updating granted roles from {} to {}".format(
108+ relation.get('roles', '') or def_str,
109+ config['roles'] or def_str))
110+
111+ hookenv.relation_set(
112+ relid, database=config['database'], roles=config['roles'])
113+
114+ if 'user' in relation:
115+ rebuild_relation(relid, unit, relation)
116+
117+ for _, relid, unit, relation in all_relations(relation_types=['db-admin']):
118+ log("{} {} {!r}".format(relid, unit, relation), DEBUG)
119+ if 'user' in relation:
120+ rebuild_relation(relid, unit, relation)
121+
122+
123+def rebuild_relation(relid, unit, relation):
124+ relname = relid.split(':')[0]
125+ unitname = unit.replace('/', '-')
126+ this_unit = hookenv.local_unit()
127+
128+ allowed_units = relation.get('allowed-units', '')
129+ if this_unit not in allowed_units.split():
130+ log("Not yet authorized on {}".format(relid), INFO)
131+ return
132+
133+ script_name = 'psql-{}-{}'.format(relname, unitname)
134+ build_script(script_name, relation)
135+ state = relation.get('state', None)
136+ if state in ('master', 'hot standby'):
137+ script_name = 'psql-{}-{}'.format(relname, state.replace(' ', '-'))
138+ build_script(script_name, relation)
139+
140+
141+def build_script(script_name, relation):
142+ # Install a wrapper to psql that connects it to the desired database
143+ # by default. One wrapper per unit per relation.
144+ script_path = os.path.abspath(os.path.join(SCRIPT_DIR, script_name))
145+ pgpass_path = os.path.abspath(os.path.join(PGPASS_DIR, script_name))
146+ script = dedent("""\
147+ #!/bin/sh
148+ exec env \\
149+ PGHOST={host} PGPORT={port} PGDATABASE={database} \\
150+ PGUSER={user} PGPASSFILE={pgpass} \\
151+ psql $@
152+ """).format(
153+ host=relation['host'],
154+ port=relation['port'],
155+ database=relation.get('database', ''), # db-admin has no database
156+ user=relation['user'],
157+ pgpass=pgpass_path)
158+ log("Generating wrapper {}".format(script_path), INFO)
159+ host.write_file(
160+ script_path, script, owner="ubuntu", group="ubuntu", perms=0o700)
161+
162+ # The wrapper requires access to the password, stored in a .pgpass
163+ # file so it isn't exposed in an environment variable or on the
164+ # command line.
165+ pgpass = "*:*:*:{user}:{password}".format(
166+ user=relation['user'], password=relation['password'])
167+ host.write_file(
168+ pgpass_path, pgpass, owner="ubuntu", group="ubuntu", perms=0o400)
169+
170+
171+hooks = hookenv.Hooks()
172+
173+
174+@hooks.hook()
175+def install():
176+ fetch.apt_install(
177+ ['language-pack-en', 'postgresql-client', 'python-psycopg2'],
178+ fatal=True)
179+ update_system_path()
180+
181+
182+@hooks.hook()
183+def upgrade_charm():
184+ # Per Bug #1205286, we can't store scripts and passwords in the
185+ # charm directory.
186+ if os.path.exists('bin'):
187+ shutil.rmtree('bin')
188+ if os.path.exists('pgpass'):
189+ shutil.rmtree('pgpass')
190+ update_system_path()
191+ return rebuild_all_relations()
192+
193+
194+@hooks.hook(
195+ 'config-changed', 'db-admin-relation-broken',
196+ 'db-admin-relation-changed', 'db-admin-relation-joined',
197+ 'db-relation-broken', 'db-relation-changed', 'db-relation-joined')
198+def rebuild_hook():
199+ return rebuild_all_relations()
200+
201+
202+if __name__ == '__main__':
203+ hooks.execute(sys.argv)
204
205=== added symlink 'lib/test-client-charm/hooks/install'
206=== target is u'./hooks.py'
207=== added file 'lib/test-client-charm/hooks/start'
208--- lib/test-client-charm/hooks/start 1970-01-01 00:00:00 +0000
209+++ lib/test-client-charm/hooks/start 2014-10-14 08:59:11 +0000
210@@ -0,0 +1,3 @@
211+#!/bin/sh
212+# Do nothing.
213+exit 0
214
215=== added file 'lib/test-client-charm/hooks/stop'
216--- lib/test-client-charm/hooks/stop 1970-01-01 00:00:00 +0000
217+++ lib/test-client-charm/hooks/stop 2014-10-14 08:59:11 +0000
218@@ -0,0 +1,3 @@
219+#!/bin/sh
220+# Do nothing.
221+exit 0
222
223=== added symlink 'lib/test-client-charm/hooks/upgrade-charm'
224=== target is u'hooks.py'
225=== added file 'lib/test-client-charm/metadata.yaml'
226--- lib/test-client-charm/metadata.yaml 1970-01-01 00:00:00 +0000
227+++ lib/test-client-charm/metadata.yaml 2014-10-14 08:59:11 +0000
228@@ -0,0 +1,13 @@
229+name: postgresql-psql
230+summary: psql command line access to PostgreSQL services.
231+maintainer: stuart.bishop@canonical.com
232+description: |
233+ Access to related PostgreSQL services via the
234+ standard psql command line utility.
235+categories:
236+ - databases
237+requires:
238+ db:
239+ interface: pgsql
240+ db-admin:
241+ interface: pgsql
242
243=== modified file 'test.py'
244--- test.py 2014-10-14 08:59:11 +0000
245+++ test.py 2014-10-14 08:59:11 +0000
246@@ -11,6 +11,7 @@
247
248 from datetime import datetime
249 import os.path
250+import shutil
251 import signal
252 import socket
253 import subprocess
254@@ -26,8 +27,8 @@
255
256
257 SERIES = os.environ.get('SERIES', 'trusty').strip()
258-TEST_CHARM = 'local:{}/postgresql'.format(SERIES)
259-PSQL_CHARM = 'local:{}/postgresql-psql'.format(SERIES)
260+TEST_CHARM = os.path.dirname(__file__)
261+PSQL_CHARM = os.path.join(TEST_CHARM, 'lib', 'test-client-charm')
262
263
264 class NotReady(Exception):
265@@ -81,6 +82,16 @@
266 # Tests may add or change options.
267 self.pg_config = dict(version=self.VERSION, pgdg=self.PGDG)
268
269+ # Mirror charmhelpers into our support charms, since charms
270+ # can't symlink out of their subtree.
271+ here = os.path.abspath(os.path.dirname(__file__))
272+ main_charmhelpers = os.path.join(here, 'hooks', 'charmhelpers')
273+ psql_charmhelpers = os.path.join(here, 'lib', 'test-client-charm',
274+ 'hooks', 'charmhelpers')
275+ if os.path.exists(psql_charmhelpers):
276+ shutil.rmtree(psql_charmhelpers)
277+ shutil.copytree(main_charmhelpers, psql_charmhelpers)
278+
279 self.juju = self.useFixture(JujuFixture(
280 series=SERIES, reuse_machines=True,
281 do_teardown='TEST_DONT_TEARDOWN_JUJU' not in os.environ))
282
283=== modified file 'testing/jujufixture.py'
284--- testing/jujufixture.py 2014-06-10 11:36:17 +0000
285+++ testing/jujufixture.py 2014-10-14 08:59:11 +0000
286@@ -21,6 +21,7 @@
287 super(JujuFixture, self).__init__()
288
289 self.series = series
290+ assert series, 'series not set'
291
292 self.reuse_machines = reuse_machines
293
294@@ -47,6 +48,8 @@
295 def deploy(self, charm, name=None, num_units=1, config=None):
296 cmd = ['deploy']
297
298+ charm = self.charm_uri(charm)
299+
300 if config:
301 config_path = os.path.join(
302 self.useFixture(fixtures.TempDir()).path, 'config.yaml')
303@@ -185,6 +188,27 @@
304 if self.do_teardown:
305 self.addCleanup(self.reset)
306
307+ # Setup a temporary repository with the magic to find our charms.
308+ self.repo_dir = self.useFixture(fixtures.TempDir()).path
309+ self.useFixture(fixtures.EnvironmentVariable('JUJU_REPOSITORY',
310+ self.repo_dir))
311+ self.repo_series_dir = os.path.join(self.repo_dir, self.series)
312+ os.mkdir(self.repo_series_dir)
313+
314+ def charm_uri(self, charm):
315+ meta = yaml.safe_load(open(os.path.join(charm, 'metadata.yaml'), 'rb'))
316+ name = meta.get('name')
317+ assert name, 'charm {} has no name in metadata.yaml'.format(charm)
318+ charm_link = os.path.join(self.repo_series_dir, name)
319+
320+ # Recreate the charm link, which might have changed from the
321+ # last deploy.
322+ if os.path.exists(charm_link):
323+ os.remove(charm_link)
324+ os.symlink(charm, os.path.join(self.repo_series_dir, name))
325+
326+ return 'local:{}/{}'.format(self.series, name)
327+
328 def reset(self):
329 # Tear down any services left running that we know we spawned.
330 while True:

Subscribers

People subscribed via source and target branches