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
=== added directory 'lib/test-client-charm'
=== added file 'lib/test-client-charm/config.yaml'
--- lib/test-client-charm/config.yaml 1970-01-01 00:00:00 +0000
+++ lib/test-client-charm/config.yaml 2014-10-14 08:59:11 +0000
@@ -0,0 +1,13 @@
1options:
2 database:
3 default: ""
4 type: string
5 description: |
6 Database to connect 'db' relationships to, overriding the
7 generated default database name.
8 roles:
9 default: ""
10 type: string
11 description: |
12 Comma separated list of roles for PostgreSQL to grant to the database
13 user.
014
=== added directory 'lib/test-client-charm/hooks'
=== added symlink 'lib/test-client-charm/hooks/config-changed'
=== target is u'hooks.py'
=== added symlink 'lib/test-client-charm/hooks/db-admin-relation-broken'
=== target is u'./hooks.py'
=== added symlink 'lib/test-client-charm/hooks/db-admin-relation-changed'
=== target is u'./hooks.py'
=== added symlink 'lib/test-client-charm/hooks/db-admin-relation-joined'
=== target is u'./hooks.py'
=== added symlink 'lib/test-client-charm/hooks/db-relation-broken'
=== target is u'./hooks.py'
=== added symlink 'lib/test-client-charm/hooks/db-relation-changed'
=== target is u'./hooks.py'
=== added symlink 'lib/test-client-charm/hooks/db-relation-joined'
=== target is u'./hooks.py'
=== added file 'lib/test-client-charm/hooks/hooks.py'
--- lib/test-client-charm/hooks/hooks.py 1970-01-01 00:00:00 +0000
+++ lib/test-client-charm/hooks/hooks.py 2014-10-14 08:59:11 +0000
@@ -0,0 +1,165 @@
1#!/usr/bin/env python
2
3import os.path
4import re
5import shutil
6import sys
7from textwrap import dedent
8
9from charmhelpers.core import hookenv, host
10from charmhelpers.core.hookenv import log, DEBUG, INFO
11from charmhelpers import fetch
12
13
14CLIENT_RELATION_TYPES = frozenset(['db', 'db-admin'])
15
16DATA_DIR = os.path.join(
17 '/var/lib/units', hookenv.local_unit().replace('/', '-'))
18SCRIPT_DIR = os.path.join(DATA_DIR, 'bin')
19PGPASS_DIR = os.path.join(DATA_DIR, 'pgpass')
20
21
22def update_system_path():
23 org_lines = open('/etc/environment', 'rb').readlines()
24 env_lines = []
25
26 for line in org_lines:
27 if line.startswith('PATH=') and SCRIPT_DIR not in line:
28 line = re.sub(
29 """(['"]?)$""",
30 ":{}\\1".format(SCRIPT_DIR),
31 line, 1)
32 env_lines.append(line)
33
34 if org_lines != env_lines:
35 content = '\n'.join(env_lines)
36 host.write_file('/etc/environment', content, perms=0o644)
37
38
39def all_relations(relation_types=CLIENT_RELATION_TYPES):
40 for reltype in relation_types:
41 for relid in hookenv.relation_ids(reltype):
42 for unit in hookenv.related_units(relid):
43 yield reltype, relid, unit, hookenv.relation_get(
44 unit=unit, rid=relid)
45
46
47def rebuild_all_relations():
48 config = hookenv.config()
49
50 # Clear out old scripts and pgpass files
51 if os.path.exists(SCRIPT_DIR):
52 shutil.rmtree(SCRIPT_DIR)
53 if os.path.exists(PGPASS_DIR):
54 shutil.rmtree(PGPASS_DIR)
55 host.mkdir(DATA_DIR, perms=0o755)
56 host.mkdir(SCRIPT_DIR, perms=0o755)
57 host.mkdir(PGPASS_DIR, group='ubuntu', perms=0o750)
58
59 for _, relid, unit, relation in all_relations(relation_types=['db']):
60 log("{} {} {!r}".format(relid, unit, relation), DEBUG)
61
62 def_str = '<DEFAULT>'
63 if config['database'] != relation.get('database', ''):
64 log("Switching from database {} to {}".format(
65 relation.get('database', '') or def_str,
66 config['database'] or def_str), INFO)
67
68 if config['roles'] != relation.get('roles', ''):
69 log("Updating granted roles from {} to {}".format(
70 relation.get('roles', '') or def_str,
71 config['roles'] or def_str))
72
73 hookenv.relation_set(
74 relid, database=config['database'], roles=config['roles'])
75
76 if 'user' in relation:
77 rebuild_relation(relid, unit, relation)
78
79 for _, relid, unit, relation in all_relations(relation_types=['db-admin']):
80 log("{} {} {!r}".format(relid, unit, relation), DEBUG)
81 if 'user' in relation:
82 rebuild_relation(relid, unit, relation)
83
84
85def rebuild_relation(relid, unit, relation):
86 relname = relid.split(':')[0]
87 unitname = unit.replace('/', '-')
88 this_unit = hookenv.local_unit()
89
90 allowed_units = relation.get('allowed-units', '')
91 if this_unit not in allowed_units.split():
92 log("Not yet authorized on {}".format(relid), INFO)
93 return
94
95 script_name = 'psql-{}-{}'.format(relname, unitname)
96 build_script(script_name, relation)
97 state = relation.get('state', None)
98 if state in ('master', 'hot standby'):
99 script_name = 'psql-{}-{}'.format(relname, state.replace(' ', '-'))
100 build_script(script_name, relation)
101
102
103def build_script(script_name, relation):
104 # Install a wrapper to psql that connects it to the desired database
105 # by default. One wrapper per unit per relation.
106 script_path = os.path.abspath(os.path.join(SCRIPT_DIR, script_name))
107 pgpass_path = os.path.abspath(os.path.join(PGPASS_DIR, script_name))
108 script = dedent("""\
109 #!/bin/sh
110 exec env \\
111 PGHOST={host} PGPORT={port} PGDATABASE={database} \\
112 PGUSER={user} PGPASSFILE={pgpass} \\
113 psql $@
114 """).format(
115 host=relation['host'],
116 port=relation['port'],
117 database=relation.get('database', ''), # db-admin has no database
118 user=relation['user'],
119 pgpass=pgpass_path)
120 log("Generating wrapper {}".format(script_path), INFO)
121 host.write_file(
122 script_path, script, owner="ubuntu", group="ubuntu", perms=0o700)
123
124 # The wrapper requires access to the password, stored in a .pgpass
125 # file so it isn't exposed in an environment variable or on the
126 # command line.
127 pgpass = "*:*:*:{user}:{password}".format(
128 user=relation['user'], password=relation['password'])
129 host.write_file(
130 pgpass_path, pgpass, owner="ubuntu", group="ubuntu", perms=0o400)
131
132
133hooks = hookenv.Hooks()
134
135
136@hooks.hook()
137def install():
138 fetch.apt_install(
139 ['language-pack-en', 'postgresql-client', 'python-psycopg2'],
140 fatal=True)
141 update_system_path()
142
143
144@hooks.hook()
145def upgrade_charm():
146 # Per Bug #1205286, we can't store scripts and passwords in the
147 # charm directory.
148 if os.path.exists('bin'):
149 shutil.rmtree('bin')
150 if os.path.exists('pgpass'):
151 shutil.rmtree('pgpass')
152 update_system_path()
153 return rebuild_all_relations()
154
155
156@hooks.hook(
157 'config-changed', 'db-admin-relation-broken',
158 'db-admin-relation-changed', 'db-admin-relation-joined',
159 'db-relation-broken', 'db-relation-changed', 'db-relation-joined')
160def rebuild_hook():
161 return rebuild_all_relations()
162
163
164if __name__ == '__main__':
165 hooks.execute(sys.argv)
0166
=== added symlink 'lib/test-client-charm/hooks/install'
=== target is u'./hooks.py'
=== added file 'lib/test-client-charm/hooks/start'
--- lib/test-client-charm/hooks/start 1970-01-01 00:00:00 +0000
+++ lib/test-client-charm/hooks/start 2014-10-14 08:59:11 +0000
@@ -0,0 +1,3 @@
1#!/bin/sh
2# Do nothing.
3exit 0
04
=== added file 'lib/test-client-charm/hooks/stop'
--- lib/test-client-charm/hooks/stop 1970-01-01 00:00:00 +0000
+++ lib/test-client-charm/hooks/stop 2014-10-14 08:59:11 +0000
@@ -0,0 +1,3 @@
1#!/bin/sh
2# Do nothing.
3exit 0
04
=== added symlink 'lib/test-client-charm/hooks/upgrade-charm'
=== target is u'hooks.py'
=== added file 'lib/test-client-charm/metadata.yaml'
--- lib/test-client-charm/metadata.yaml 1970-01-01 00:00:00 +0000
+++ lib/test-client-charm/metadata.yaml 2014-10-14 08:59:11 +0000
@@ -0,0 +1,13 @@
1name: postgresql-psql
2summary: psql command line access to PostgreSQL services.
3maintainer: stuart.bishop@canonical.com
4description: |
5 Access to related PostgreSQL services via the
6 standard psql command line utility.
7categories:
8 - databases
9requires:
10 db:
11 interface: pgsql
12 db-admin:
13 interface: pgsql
014
=== modified file 'test.py'
--- test.py 2014-10-14 08:59:11 +0000
+++ test.py 2014-10-14 08:59:11 +0000
@@ -11,6 +11,7 @@
1111
12from datetime import datetime12from datetime import datetime
13import os.path13import os.path
14import shutil
14import signal15import signal
15import socket16import socket
16import subprocess17import subprocess
@@ -26,8 +27,8 @@
2627
2728
28SERIES = os.environ.get('SERIES', 'trusty').strip()29SERIES = os.environ.get('SERIES', 'trusty').strip()
29TEST_CHARM = 'local:{}/postgresql'.format(SERIES)30TEST_CHARM = os.path.dirname(__file__)
30PSQL_CHARM = 'local:{}/postgresql-psql'.format(SERIES)31PSQL_CHARM = os.path.join(TEST_CHARM, 'lib', 'test-client-charm')
3132
3233
33class NotReady(Exception):34class NotReady(Exception):
@@ -81,6 +82,16 @@
81 # Tests may add or change options.82 # Tests may add or change options.
82 self.pg_config = dict(version=self.VERSION, pgdg=self.PGDG)83 self.pg_config = dict(version=self.VERSION, pgdg=self.PGDG)
8384
85 # Mirror charmhelpers into our support charms, since charms
86 # can't symlink out of their subtree.
87 here = os.path.abspath(os.path.dirname(__file__))
88 main_charmhelpers = os.path.join(here, 'hooks', 'charmhelpers')
89 psql_charmhelpers = os.path.join(here, 'lib', 'test-client-charm',
90 'hooks', 'charmhelpers')
91 if os.path.exists(psql_charmhelpers):
92 shutil.rmtree(psql_charmhelpers)
93 shutil.copytree(main_charmhelpers, psql_charmhelpers)
94
84 self.juju = self.useFixture(JujuFixture(95 self.juju = self.useFixture(JujuFixture(
85 series=SERIES, reuse_machines=True,96 series=SERIES, reuse_machines=True,
86 do_teardown='TEST_DONT_TEARDOWN_JUJU' not in os.environ))97 do_teardown='TEST_DONT_TEARDOWN_JUJU' not in os.environ))
8798
=== modified file 'testing/jujufixture.py'
--- testing/jujufixture.py 2014-06-10 11:36:17 +0000
+++ testing/jujufixture.py 2014-10-14 08:59:11 +0000
@@ -21,6 +21,7 @@
21 super(JujuFixture, self).__init__()21 super(JujuFixture, self).__init__()
2222
23 self.series = series23 self.series = series
24 assert series, 'series not set'
2425
25 self.reuse_machines = reuse_machines26 self.reuse_machines = reuse_machines
2627
@@ -47,6 +48,8 @@
47 def deploy(self, charm, name=None, num_units=1, config=None):48 def deploy(self, charm, name=None, num_units=1, config=None):
48 cmd = ['deploy']49 cmd = ['deploy']
4950
51 charm = self.charm_uri(charm)
52
50 if config:53 if config:
51 config_path = os.path.join(54 config_path = os.path.join(
52 self.useFixture(fixtures.TempDir()).path, 'config.yaml')55 self.useFixture(fixtures.TempDir()).path, 'config.yaml')
@@ -185,6 +188,27 @@
185 if self.do_teardown:188 if self.do_teardown:
186 self.addCleanup(self.reset)189 self.addCleanup(self.reset)
187190
191 # Setup a temporary repository with the magic to find our charms.
192 self.repo_dir = self.useFixture(fixtures.TempDir()).path
193 self.useFixture(fixtures.EnvironmentVariable('JUJU_REPOSITORY',
194 self.repo_dir))
195 self.repo_series_dir = os.path.join(self.repo_dir, self.series)
196 os.mkdir(self.repo_series_dir)
197
198 def charm_uri(self, charm):
199 meta = yaml.safe_load(open(os.path.join(charm, 'metadata.yaml'), 'rb'))
200 name = meta.get('name')
201 assert name, 'charm {} has no name in metadata.yaml'.format(charm)
202 charm_link = os.path.join(self.repo_series_dir, name)
203
204 # Recreate the charm link, which might have changed from the
205 # last deploy.
206 if os.path.exists(charm_link):
207 os.remove(charm_link)
208 os.symlink(charm, os.path.join(self.repo_series_dir, name))
209
210 return 'local:{}/{}'.format(self.series, name)
211
188 def reset(self):212 def reset(self):
189 # Tear down any services left running that we know we spawned.213 # Tear down any services left running that we know we spawned.
190 while True:214 while True:

Subscribers

People subscribed via source and target branches