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

Proposed by Stuart Bishop
Status: Rejected
Rejected by: Stuart Bishop
Proposed branch: lp:~stub/charms/precise/postgresql/tests
Merge into: lp:charms/postgresql
Prerequisite: lp:~stub/charms/precise/postgresql/fix-races
Diff against target: 322 lines (+24/-199)
2 files modified
hooks/hooks.py (+10/-8)
test.py (+14/-191)
To merge this branch: bzr merge lp:~stub/charms/precise/postgresql/tests
Reviewer Review Type Date Requested Status
charmers Pending
Review via email: mp+190081@code.launchpad.net

Description of the change

Migrating the Juju fixture into charm-helpers.

Also some delinting.

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

This should probably be dropped in favor of amulet (https://launchpad.net/amulet)

86. By Stuart Bishop

Resolve conflicts

87. By Stuart Bishop

Put back testing support

Unmerged revisions

150. By Stuart Bishop

Merged cleanups into tests.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'hooks/hooks.py'
2--- hooks/hooks.py 2013-09-23 09:39:17 +0000
3+++ hooks/hooks.py 2013-10-09 10:21:55 +0000
4@@ -18,6 +18,7 @@
5 import yaml
6 from yaml.constructor import ConstructorError
7
8+from charmhelpers import fetch
9 from charmhelpers.core import hookenv, host
10 from charmhelpers.core.hookenv import (
11 CRITICAL, ERROR, WARNING, INFO, DEBUG,
12@@ -25,6 +26,7 @@
13
14 hooks = hookenv.Hooks()
15
16+
17 # jinja2 may not be importable until the install hook has installed the
18 # required packages.
19 def Template(*args, **kw):
20@@ -505,13 +507,12 @@
21 admin_ip_list = [config_data["admin_addresses"]]
22
23 for admin_ip in admin_ip_list:
24- admin_host = {'database':'all',
25- 'user':'all',
26- 'private-address':munge_address(admin_ip),
27- }
28+ admin_host = {
29+ 'database': 'all',
30+ 'user': 'all',
31+ 'private-address': munge_address(admin_ip)}
32 relation_data.append(admin_host)
33
34-
35 pg_hba_template = Template(open("templates/pg_hba.conf.tmpl").read())
36 host.write_file(
37 postgresql_hba, pg_hba_template.render(access_list=relation_data),
38@@ -826,8 +827,8 @@
39 "python-psycopg2", "postgresql-contrib", "postgresql-plpython",
40 "postgresql-%s-debversion" % config_data["version"]]
41 packages.extend((hookenv.config('extra-packages') or '').split())
42- packages = host.filter_installed_packages(packages)
43- host.apt_install(packages, fatal=True)
44+ packages = fetch.filter_installed_packages(packages)
45+ fetch.apt_install(packages, fatal=True)
46
47 if not 'state' in local_state:
48 # Fresh installation. Because this function is invoked by both
49@@ -1063,7 +1064,7 @@
50 # A more complex approach is for the first database unit that joins
51 # the relation to generate the usernames and passwords and publish
52 # this to the relation. Subsequent units can retrieve this
53-# information and republish it. Of course, the master unit also
54+# information and republish it. Of course, the master unit also
55 # creates the database and users when it joins the relation.
56 # This approach should work reliably on the server side. However,
57 # there is a window from when a slave unit joins a client relation
58@@ -1101,6 +1102,7 @@
59 # slave replication-relation-changed (noop; slave not yet joined db rel)
60 # slave db-relation-joined (republish)
61
62+
63 @hooks.hook('db-relation-joined', 'db-relation-changed')
64 def db_relation_joined_changed():
65 if local_state['state'] == 'hot standby':
66
67=== modified file 'test.py'
68--- test.py 2013-08-23 08:03:02 +0000
69+++ test.py 2013-10-09 10:21:55 +0000
70@@ -5,18 +5,22 @@
71
72 Usage:
73 juju bootstrap
74- TEST_DEBUG_FILE=test-debug.log TEST_TIMEOUT=900 ./test.py -v
75+ TEST_TIMEOUT=900 ./test.py -v
76 juju destroy-environment
77 """
78
79-import fixtures
80-import json
81 import os.path
82 import subprocess
83+import sys
84+import time
85+import unittest
86+
87+import fixtures
88 import testtools
89 from testtools.content import text_content
90-import time
91-import unittest
92+
93+sys.path.append(os.path.join(os.path.dirname(__file__), 'hooks'))
94+from charmhelpers.testing.jujufixture import JujuFixture, run
95
96
97 SERIES = 'precise'
98@@ -24,191 +28,16 @@
99 PSQL_CHARM = 'local:postgresql-psql'
100
101
102-def DEBUG(msg):
103- """Allow us to watch these slow tests as they run."""
104- debug_file = os.environ.get('TEST_DEBUG_FILE', '')
105- if debug_file:
106- with open(debug_file, 'a') as f:
107- f.write('{}> {}\n'.format(time.ctime(), msg))
108- f.flush()
109-
110-
111-def _run(detail_collector, cmd, input=''):
112- DEBUG("Running {}".format(' '.join(cmd)))
113- try:
114- proc = subprocess.Popen(
115- cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
116- stderr=subprocess.PIPE)
117- except subprocess.CalledProcessError, x:
118- DEBUG("exception: {!r}".format(x))
119- DEBUG("stderr: {}".format(proc.stderr.read()))
120- raise
121-
122- (out, err) = proc.communicate(input)
123- if out:
124- DEBUG("stdout: {}".format(out))
125- detail_collector.addDetail('stdout', text_content(out))
126- if err:
127- DEBUG("stderr: {}".format(err))
128- detail_collector.addDetail('stderr', text_content(err))
129- if proc.returncode != 0:
130- DEBUG("rv: {}".format(proc.returncode))
131- raise subprocess.CalledProcessError(
132- proc.returncode, cmd, err)
133- return out
134-
135-
136-class JujuFixture(fixtures.Fixture):
137- """Interact with juju. Assumes juju environment is bootstrapped."""
138- _deployed_charms = set()
139-
140- def do(self, cmd):
141- cmd = ['juju'] + cmd
142- _run(self, cmd)
143-
144- def get_result(self, cmd):
145- cmd = ['juju'] + cmd + ['--format=json']
146- out = _run(self, cmd)
147- if out:
148- return json.loads(out)
149- return None
150-
151- def deploy(self, charm, name=None, num_units=1):
152- # The first time we deploy a local: charm in the test run, it
153- # needs to deploy with --update to ensure we are testing the
154- # desired revision of the charm. Subsequent deploys we do not
155- # use --update to avoid overhead and needless incrementing of the
156- # revision number.
157- if not charm.startswith('local:') or charm in self._deployed_charms:
158- cmd = ['deploy']
159- else:
160- cmd = ['deploy', '-u']
161- self._deployed_charms.add(charm)
162-
163- if num_units > 1:
164- cmd.extend(['-n', str(num_units)])
165-
166- cmd.append(charm)
167-
168- if name:
169- cmd.append(name)
170-
171- self.do(cmd)
172-
173- # The most recent environment status, updated by refresh_status()
174- status = None
175-
176- def refresh_status(self):
177- self.status = self.get_result(['status'])
178- return self.status
179-
180- def wait_until_ready(self, extra=45):
181- ready = False
182- while not ready:
183- self.refresh_status()
184- ready = True
185- for service in self.status['services']:
186- if self.status['services'][service].get('life', '') == 'dying':
187- ready = False
188- units = self.status['services'][service].get('units', {})
189- for unit in units.keys():
190- agent_state = units[unit].get('agent-state', '')
191- if agent_state == 'error':
192- raise RuntimeError('{} error: {}'.format(
193- unit, units[unit].get('agent-state-info','')))
194- if agent_state != 'started':
195- ready = False
196- # Unfortunately, there is no way to tell when a system is
197- # actually ready for us to test. Juju only tells us that a
198- # relation has started being setup, and that no errors have been
199- # encountered yet. It utterly fails to inform us when the
200- # cascade of hooks this triggers has finished and the
201- # environment is in a stable and actually testable state.
202- # So as a work around for Bug #1200267, we need to sleep long
203- # enough that our system is probably stable. This means we have
204- # extremely slow and flaky tests, but that is possibly better
205- # than no tests.
206- time.sleep(extra)
207-
208- def setUp(self):
209- DEBUG("JujuFixture.setUp()")
210- super(JujuFixture, self).setUp()
211- self.reset()
212- # Optionally, don't teardown services and machines after running
213- # a test. If a subsequent test is run, they will be torn down at
214- # that point. This option is only useful when running a single
215- # test, or when the test harness is set to abort after the first
216- # failed test.
217- if not os.environ.get('TEST_DONT_TEARDOWN_JUJU', False):
218- self.addCleanup(self.reset)
219-
220- def reset(self):
221- DEBUG("JujuFixture.reset()")
222- # Tear down any services left running.
223- found_services = False
224- self.refresh_status()
225- for service in self.status['services']:
226- found_services = True
227- # It is an error to destroy a dying service.
228- if self.status['services'][service].get('life', '') != 'dying':
229- self.do(['destroy-service', service])
230-
231- # Per Bug #1190250 (WONTFIX), we need to wait for dying services
232- # to die before we can continue.
233- if found_services:
234- self.wait_until_ready(0)
235-
236- # We shouldn't reuse machines, as we have no guarantee they are
237- # still in a usable state, so tear them down too. Per
238- # Bug #1190492 (INVALID), in the future this will be much nicer
239- # when we can use containers for isolation and can happily reuse
240- # machines.
241- dirty_machines = [
242- m for m in self.status['machines'].keys() if m != '0']
243- if dirty_machines:
244- self.do(['terminate-machine'] + dirty_machines)
245-
246-
247-class LocalCharmRepositoryFixture(fixtures.Fixture):
248- """Create links so the given directory can be deployed as a charm."""
249- def __init__(self, path=None):
250- if path is None:
251- path = os.getcwd()
252- self.local_repo_path = os.path.abspath(path)
253-
254- def setUp(self):
255- super(LocalCharmRepositoryFixture, self).setUp()
256-
257- series_dir = os.path.join(self.local_repo_path, SERIES)
258- charm_dir = os.path.join(series_dir, TEST_CHARM)
259-
260- if not os.path.exists(series_dir):
261- os.mkdir(series_dir, 0o700)
262- self.addCleanup(os.rmdir, series_dir)
263-
264- if not os.path.exists(charm_dir):
265- os.symlink(self.local_repo_path, charm_dir)
266- self.addCleanup(os.remove, charm_dir)
267-
268- self.useFixture(fixtures.EnvironmentVariable(
269- 'JUJU_REPOSITORY', self.local_repo_path))
270-
271-
272 class PostgreSQLCharmTestCase(testtools.TestCase, fixtures.TestWithFixtures):
273
274 def setUp(self):
275 super(PostgreSQLCharmTestCase, self).setUp()
276
277- self.juju = self.useFixture(JujuFixture())
278-
279- ## Disabled until postgresql-psql is in the charm store.
280- ## Otherwise, we need to make the local:postgresql-psql charm
281- ## discoverable.
282- ## self.useFixture(LocalCharmRepositoryFixture())
283+ self.juju = self.useFixture(JujuFixture(
284+ reuse_machines=True,
285+ do_teardown='TEST_DONT_TEARDOWN_JUJU' not in os.environ))
286
287 # If the charms fail, we don't want tests to hang indefinitely.
288- # We might need to increase this in some environments or if the
289- # environment doesn't have enough machines warmed up.
290 timeout = int(os.environ.get('TEST_TIMEOUT', 900))
291 self.useFixture(fixtures.Timeout(timeout, gentle=True))
292
293@@ -254,9 +83,7 @@
294 # Due to Bug #1191079, we need to send the whole remote command
295 # as a single argument.
296 ' '.join(psql_cmd + psql_args)]
297- DEBUG("SQL {}".format(sql))
298- out = _run(self, cmd, input=sql)
299- DEBUG("OUT {}".format(out))
300+ out = run(self, cmd, input=sql)
301 result = [line.split(',') for line in out.splitlines()]
302 self.addDetail('sql', text_content(repr((sql, result))))
303 return result
304@@ -266,7 +93,7 @@
305 # Due to Bug #1191079, we need to send the whole remote command
306 # as a single argument.
307 'sudo pg_ctlcluster 9.1 main -force {}'.format(command)]
308- _run(self, cmd)
309+ run(self, cmd)
310
311 def test_basic(self):
312 '''Connect to a a single unit service via the db relationship.'''
313@@ -275,10 +102,6 @@
314 self.juju.do(['add-relation', 'postgresql:db', 'psql:db'])
315 self.juju.wait_until_ready()
316
317- # There a race condition here, as hooks may still be running
318- # from adding the relation. I'm protected here as 'juju status'
319- # takes about 25 seconds to run from here to my test cloud but
320- # others might not be so 'lucky'.
321 result = self.sql('SELECT TRUE')
322 self.assertEqual(result, [['t']])
323

Subscribers

People subscribed via source and target branches