Merge lp:~stub/charms/precise/postgresql/charm-helpers into lp:charms/postgresql

Proposed by Stuart Bishop
Status: Merged
Merged at revision: 69
Proposed branch: lp:~stub/charms/precise/postgresql/charm-helpers
Merge into: lp:charms/postgresql
Diff against target: 217 lines (+1/-197)
3 files modified
charm-helpers.yaml (+1/-2)
hooks/charmhelpers/testing/__init__.py (+0/-1)
hooks/charmhelpers/testing/jujufixture.py (+0/-194)
To merge this branch: bzr merge lp:~stub/charms/precise/postgresql/charm-helpers
Reviewer Review Type Date Requested Status
Stuart Bishop (community) Approve
Review via email: mp+190077@code.launchpad.net

Description of the change

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

Revert to trunk charm-helpers

Revision history for this message
Stuart Bishop (stub) wrote :

Reverted to trunk

Revision history for this message
Stuart Bishop (stub) :
review: Approve
71. By Stuart Bishop

Merged postgresql into charm-helpers.

72. By Stuart Bishop

Remove old testing package

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'charm-helpers.yaml'
--- charm-helpers.yaml 2013-09-24 11:09:35 +0000
+++ charm-helpers.yaml 2013-10-10 19:35:20 +0000
@@ -1,6 +1,5 @@
1destination: hooks/charmhelpers1destination: hooks/charmhelpers
2branch: lp:~stub/charm-helpers/test-harness2branch: lp:charm-helpers
3include:3include:
4 - core4 - core
5 - testing
6 - fetch5 - fetch
76
=== removed directory 'hooks/charmhelpers/testing'
=== removed file 'hooks/charmhelpers/testing/__init__.py'
--- hooks/charmhelpers/testing/__init__.py 2013-09-24 11:09:35 +0000
+++ hooks/charmhelpers/testing/__init__.py 1970-01-01 00:00:00 +0000
@@ -1,1 +0,0 @@
1from jujufixture import *
20
=== removed file 'hooks/charmhelpers/testing/jujufixture.py'
--- hooks/charmhelpers/testing/jujufixture.py 2013-09-24 11:09:35 +0000
+++ hooks/charmhelpers/testing/jujufixture.py 1970-01-01 00:00:00 +0000
@@ -1,194 +0,0 @@
1import json
2import subprocess
3import time
4
5import fixtures
6from testtools.content import text_content
7
8
9__all__ = ['JujuFixture', 'run']
10
11
12class JujuFixture(fixtures.Fixture):
13 """Interact with juju.
14
15 Assumes juju environment is bootstrapped.
16 """
17
18 def __init__(self, reuse_machines=False, do_teardown=True):
19 super(JujuFixture, self).__init__()
20
21 self._deployed_charms = set()
22
23 self.reuse_machines = reuse_machines
24
25 # Optionally, don't teardown services and machines after running
26 # a test. If a subsequent test is run, they will be torn down at
27 # that point. This option is only useful when running a single
28 # test, or when the test harness is set to abort after the first
29 # failed test.
30 self.do_teardown = do_teardown
31
32 self._deployed_services = set()
33
34 def do(self, cmd):
35 cmd = ['juju'] + cmd
36 run(self, cmd)
37
38 def get_result(self, cmd):
39 cmd = ['juju'] + cmd + ['--format=json']
40 out = run(self, cmd)
41 if out:
42 return json.loads(out)
43 return None
44
45 def deploy(self, charm, name=None, num_units=1):
46 # The first time we deploy a local: charm in the test run, it
47 # needs to deploy with --update to ensure we are testing the
48 # desired revision of the charm. Subsequent deploys we do not
49 # use --update to avoid overhead and needless incrementing of the
50 # revision number.
51 if not charm.startswith('local:') or charm in self._deployed_charms:
52 cmd = ['deploy']
53 else:
54 cmd = ['deploy', '-u']
55 self._deployed_charms.add(charm)
56
57 cmd.append(charm)
58
59 if name is None:
60 name = charm.split(':', 1)[-1]
61
62 cmd.append(name)
63 self._deployed_services.add(name)
64
65 if self.reuse_machines and self._free_machines:
66 cmd.extend(['--to', str(self._free_machines.pop())])
67 self.do(cmd)
68 if num_units > 1:
69 self.add_unit(charm, name, num_units - 1)
70 else:
71 cmd.extend(['-n', str(num_units)])
72 self.do(cmd)
73
74 def add_unit(self, charm, name=None, num_units = 1):
75 if name is None:
76 name = charm.split(':', 1)[-1]
77
78 num_units_spawned = 0
79 while self.reuse_machines and self._free_machines:
80 cmd = ['add-unit', '--to', str(self._free_machines.pop()), name]
81 self.do(cmd)
82 num_units_spawned += 1
83 if num_units_spawned == num_units:
84 return
85
86 cmd = ['add-unit', '-n', str(num_units - num_units_spawned), name]
87 self.do(cmd)
88
89 # The most recent environment status, updated by refresh_status()
90 status = None
91
92 def refresh_status(self):
93 self.status = self.get_result(['status'])
94
95 self._free_machines = set(
96 int(k) for k in self.status['machines'].keys()
97 if k != '0')
98 for service in self.status.get('services', {}).values():
99 for unit in service.get('units', []):
100 if 'machine' in unit:
101 self._free_machines.remove(int(unit['machine']))
102
103 return self.status
104
105 def wait_until_ready(self, extra=45):
106 ready = False
107 while not ready:
108 self.refresh_status()
109 ready = True
110 for service in self.status['services']:
111 if self.status['services'][service].get('life', '') == 'dying':
112 ready = False
113 units = self.status['services'][service].get('units', {})
114 for unit in units.keys():
115 agent_state = units[unit].get('agent-state', '')
116 if agent_state == 'error':
117 raise RuntimeError('{} error: {}'.format(
118 unit, units[unit].get('agent-state-info','')))
119 if agent_state != 'started':
120 ready = False
121 time.sleep(1)
122 # Unfortunately, there is no way to tell when a system is
123 # actually ready for us to test. Juju only tells us that a
124 # relation has started being setup, and that no errors have been
125 # encountered yet. It utterly fails to inform us when the
126 # cascade of hooks this triggers has finished and the
127 # environment is in a stable and actually testable state.
128 # So as a work around for Bug #1200267, we need to sleep long
129 # enough that our system is probably stable. This means we have
130 # extremely slow and flaky tests, but that is possibly better
131 # than no tests.
132 time.sleep(extra)
133
134 def setUp(self):
135 super(JujuFixture, self).setUp()
136 self.reset()
137 if self.do_teardown:
138 self.addCleanup(self.reset)
139
140 def reset(self):
141 # Tear down any services left running that we know we spawned.
142 while True:
143 found_services = False
144 self.refresh_status()
145
146 # Kill any services started by the deploy() method.
147 for service_name, service in self.status.get(
148 'services', {}).items():
149 if service_name in self._deployed_services:
150 found_services = True
151 if service.get('life', '') != 'dying':
152 self.do(['destroy-service', service_name])
153 # If any units have failed hooks, unstick them.
154 for unit_name, unit in service.get('units', {}).items():
155 if unit.get('agent-state', None) == 'error':
156 self.do(['resolved', unit_name])
157 if not found_services:
158 break
159 time.sleep(1)
160
161 self._deployed_services = set()
162
163 # We need to wait for dying services
164 # to die before we can continue.
165 if found_services:
166 self.wait_until_ready(0)
167
168 # We shouldn't reuse machines, as we have no guarantee they are
169 # still in a usable state, so tear them down too. Per
170 # Bug #1190492 (INVALID), in the future this will be much nicer
171 # when we can use containers for isolation and can happily reuse
172 # machines.
173 if not self.reuse_machines:
174 self.do(['terminate-machine'] + list(self._free_machines))
175
176
177def run(detail_collector, cmd, input=''):
178 try:
179 proc = subprocess.Popen(
180 cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
181 stderr=subprocess.PIPE)
182 except subprocess.CalledProcessError, x:
183 raise
184
185 (out, err) = proc.communicate(input)
186 if out:
187 detail_collector.addDetail('stdout', text_content(out))
188 if err:
189 detail_collector.addDetail('stderr', text_content(err))
190 if proc.returncode != 0:
191 raise subprocess.CalledProcessError(
192 proc.returncode, cmd, err)
193 return out
194

Subscribers

People subscribed via source and target branches