28.6.1
by Stuart Bishop
Sketching out test framework |
1 |
#!/usr/bin/python
|
2 |
||
28.6.12
by Stuart Bishop
Basic test working |
3 |
"""
|
46.5.32
by Stuart Bishop
Fix election test |
4 |
Test the PostgreSQL charm.
|
5 |
||
6 |
Usage:
|
|
7 |
juju bootstrap
|
|
8 |
TEST_DEBUG_FILE=test-debug.log TEST_TIMEOUT=900 ./test.py -v
|
|
9 |
juju destroy-environment
|
|
28.6.12
by Stuart Bishop
Basic test working |
10 |
"""
|
11 |
||
28.6.1
by Stuart Bishop
Sketching out test framework |
12 |
import fixtures |
13 |
import json |
|
28.6.2
by Stuart Bishop
Define local repository |
14 |
import os.path |
28.6.1
by Stuart Bishop
Sketching out test framework |
15 |
import subprocess |
16 |
import testtools |
|
28.6.11
by Stuart Bishop
WIP |
17 |
from testtools.content import text_content |
28.6.12
by Stuart Bishop
Basic test working |
18 |
import time |
28.6.1
by Stuart Bishop
Sketching out test framework |
19 |
import unittest |
20 |
||
21 |
||
28.6.2
by Stuart Bishop
Define local repository |
22 |
SERIES = 'precise' |
28.6.8
by Stuart Bishop
WIP |
23 |
TEST_CHARM = 'local:postgresql' |
46.5.27
by Stuart Bishop
WIP |
24 |
PSQL_CHARM = 'cs:postgresql-psql' |
28.6.2
by Stuart Bishop
Define local repository |
25 |
|
26 |
||
28.6.12
by Stuart Bishop
Basic test working |
27 |
def DEBUG(msg): |
28 |
"""Allow us to watch these slow tests as they run."""
|
|
29 |
debug_file = os.environ.get('TEST_DEBUG_FILE', '') |
|
30 |
if debug_file: |
|
31 |
with open(debug_file, 'a') as f: |
|
32 |
f.write('{}> {}\n'.format(time.ctime(), msg)) |
|
33 |
f.flush() |
|
34 |
||
35 |
||
28.6.11
by Stuart Bishop
WIP |
36 |
def _run(detail_collector, cmd, input=''): |
28.6.12
by Stuart Bishop
Basic test working |
37 |
DEBUG("Running {}".format(' '.join(cmd))) |
38 |
try: |
|
39 |
proc = subprocess.Popen( |
|
40 |
cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, |
|
41 |
stderr=subprocess.PIPE) |
|
42 |
except subprocess.CalledProcessError, x: |
|
43 |
DEBUG("exception: {!r}".format(x)) |
|
44 |
DEBUG("stderr: {}".format(proc.stderr.read())) |
|
45 |
raise
|
|
46 |
||
28.6.11
by Stuart Bishop
WIP |
47 |
(out, err) = proc.communicate(input) |
48 |
if out: |
|
28.6.12
by Stuart Bishop
Basic test working |
49 |
DEBUG("stdout: {}".format(out)) |
28.6.11
by Stuart Bishop
WIP |
50 |
detail_collector.addDetail('stdout', text_content(out)) |
51 |
if err: |
|
28.6.12
by Stuart Bishop
Basic test working |
52 |
DEBUG("stderr: {}".format(err)) |
28.6.11
by Stuart Bishop
WIP |
53 |
detail_collector.addDetail('stderr', text_content(err)) |
54 |
if proc.returncode != 0: |
|
28.6.12
by Stuart Bishop
Basic test working |
55 |
DEBUG("rv: {}".format(proc.returncode)) |
28.6.11
by Stuart Bishop
WIP |
56 |
raise subprocess.CalledProcessError( |
57 |
proc.returncode, cmd, err) |
|
58 |
return out |
|
59 |
||
60 |
||
28.6.1
by Stuart Bishop
Sketching out test framework |
61 |
class JujuFixture(fixtures.Fixture): |
28.6.2
by Stuart Bishop
Define local repository |
62 |
"""Interact with juju. Assumes juju environment is bootstrapped."""
|
46.2.13
by Stuart Bishop
Smarter deploy to save a few mins startup overhead |
63 |
_deployed_charms = set() |
64 |
||
28.6.1
by Stuart Bishop
Sketching out test framework |
65 |
def do(self, cmd): |
28.6.11
by Stuart Bishop
WIP |
66 |
cmd = ['juju'] + cmd |
67 |
_run(self, cmd) |
|
28.6.1
by Stuart Bishop
Sketching out test framework |
68 |
|
69 |
def get_result(self, cmd): |
|
28.6.11
by Stuart Bishop
WIP |
70 |
cmd = ['juju'] + cmd + ['--format=json'] |
71 |
out = _run(self, cmd) |
|
72 |
if out: |
|
73 |
return json.loads(out) |
|
28.6.1
by Stuart Bishop
Sketching out test framework |
74 |
return None |
75 |
||
46.2.13
by Stuart Bishop
Smarter deploy to save a few mins startup overhead |
76 |
def deploy(self, charm, name=None, num_units=1): |
77 |
# The first time we deploy a charm in the test run, it needs to
|
|
78 |
# deploy with --update to ensure we are testing the desired
|
|
79 |
# revision of the charm. Subsequent deploys we do not use
|
|
80 |
# --update to avoid overhead and needless incrementing of the
|
|
81 |
# revision number.
|
|
46.5.27
by Stuart Bishop
WIP |
82 |
if charm.startswith('cs:') or charm in self._deployed_charms: |
83 |
cmd = ['deploy'] |
|
84 |
else: |
|
46.2.13
by Stuart Bishop
Smarter deploy to save a few mins startup overhead |
85 |
cmd = ['deploy', '-u'] |
86 |
self._deployed_charms.add(charm) |
|
87 |
||
88 |
if num_units > 1: |
|
89 |
cmd.extend(['-n', str(num_units)]) |
|
90 |
||
46.2.14
by Stuart Bishop
Fix deploy helper |
91 |
cmd.append(charm) |
92 |
||
46.2.13
by Stuart Bishop
Smarter deploy to save a few mins startup overhead |
93 |
if name: |
94 |
cmd.append(name) |
|
95 |
||
96 |
self.do(cmd) |
|
97 |
||
28.6.8
by Stuart Bishop
WIP |
98 |
# The most recent environment status, updated by refresh_status()
|
99 |
status = None |
|
100 |
||
101 |
def refresh_status(self): |
|
102 |
self.status = self.get_result(['status']) |
|
28.6.11
by Stuart Bishop
WIP |
103 |
return self.status |
28.6.8
by Stuart Bishop
WIP |
104 |
|
105 |
def wait_until_ready(self): |
|
106 |
ready = False |
|
107 |
while not ready: |
|
108 |
self.refresh_status() |
|
109 |
ready = True |
|
28.6.11
by Stuart Bishop
WIP |
110 |
for service in self.status['services']: |
46.2.5
by Stuart Bishop
Fixes for gojuju |
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', '') |
|
46.5.15
by Stuart Bishop
WIP |
116 |
if agent_state == 'error': |
117 |
raise RuntimeError('{} error: {}'.format( |
|
118 |
unit, units[unit].get('agent-state-info',''))) |
|
28.6.11
by Stuart Bishop
WIP |
119 |
if agent_state != 'started': |
120 |
ready = False |
|
46.5.28
by Stuart Bishop
WIP |
121 |
# Wait a little longer, as we have no way of telling
|
122 |
# if relationship hooks have finished running.
|
|
123 |
time.sleep(10) |
|
28.6.2
by Stuart Bishop
Define local repository |
124 |
|
125 |
def setUp(self): |
|
28.6.14
by Stuart Bishop
A little logging |
126 |
DEBUG("JujuFixture.setUp()") |
28.6.2
by Stuart Bishop
Define local repository |
127 |
super(JujuFixture, self).setUp() |
28.6.11
by Stuart Bishop
WIP |
128 |
self.reset() |
28.6.2
by Stuart Bishop
Define local repository |
129 |
self.addCleanup(self.reset) |
130 |
||
131 |
def reset(self): |
|
28.6.14
by Stuart Bishop
A little logging |
132 |
DEBUG("JujuFixture.reset()") |
28.6.1
by Stuart Bishop
Sketching out test framework |
133 |
# Tear down any services left running.
|
46.2.13
by Stuart Bishop
Smarter deploy to save a few mins startup overhead |
134 |
found_services = False |
28.6.11
by Stuart Bishop
WIP |
135 |
self.refresh_status() |
136 |
for service in self.status['services']: |
|
46.2.13
by Stuart Bishop
Smarter deploy to save a few mins startup overhead |
137 |
found_services = True |
46.2.5
by Stuart Bishop
Fixes for gojuju |
138 |
# It is an error to destroy a dying service.
|
139 |
if self.status['services'][service].get('life', '') != 'dying': |
|
140 |
self.do(['destroy-service', service]) |
|
141 |
||
46.2.12
by Stuart Bishop
Update comments from bug feedback and better initialization |
142 |
# Per Bug #1190250 (WONTFIX), we need to wait for dying services
|
143 |
# to die before we can continue.
|
|
46.2.13
by Stuart Bishop
Smarter deploy to save a few mins startup overhead |
144 |
if found_services: |
145 |
self.wait_until_ready() |
|
46.2.5
by Stuart Bishop
Fixes for gojuju |
146 |
|
46.2.6
by Stuart Bishop
Improve comment |
147 |
# We shouldn't reuse machines, as we have no guarantee they are
|
46.2.12
by Stuart Bishop
Update comments from bug feedback and better initialization |
148 |
# still in a usable state, so tear them down too. Per
|
149 |
# Bug #1190492 (INVALID), in the future this will be much nicer
|
|
150 |
# when we can use containers for isolation and can happily reuse
|
|
151 |
# machines.
|
|
46.2.5
by Stuart Bishop
Fixes for gojuju |
152 |
dirty_machines = [ |
153 |
m for m in self.status['machines'].keys() if m != '0'] |
|
154 |
if dirty_machines: |
|
46.2.20
by Stuart Bishop
Prefer syntax compatible with pyjuju |
155 |
self.do(['terminate-machine'] + dirty_machines) |
28.6.1
by Stuart Bishop
Sketching out test framework |
156 |
|
157 |
||
28.6.2
by Stuart Bishop
Define local repository |
158 |
class LocalCharmRepositoryFixture(fixtures.Fixture): |
159 |
"""Create links so the given directory can be deployed as a charm."""
|
|
160 |
def __init__(self, path=None): |
|
161 |
if path is None: |
|
162 |
path = os.getcwd() |
|
163 |
self.local_repo_path = os.path.abspath(path) |
|
164 |
||
165 |
def setUp(self): |
|
166 |
super(LocalCharmRepositoryFixture, self).setUp() |
|
167 |
||
168 |
series_dir = os.path.join(self.local_repo_path, SERIES) |
|
28.6.8
by Stuart Bishop
WIP |
169 |
charm_dir = os.path.join(series_dir, TEST_CHARM) |
28.6.2
by Stuart Bishop
Define local repository |
170 |
|
171 |
if not os.path.exists(series_dir): |
|
172 |
os.mkdir(series_dir, 0o700) |
|
173 |
self.addCleanup(os.rmdir, series_dir) |
|
174 |
||
175 |
if not os.path.exists(charm_dir): |
|
176 |
os.symlink(self.local_repo_path, charm_dir) |
|
177 |
self.addCleanup(os.remove, charm_dir) |
|
178 |
||
179 |
self.useFixture(fixtures.EnvironmentVariable( |
|
180 |
'JUJU_REPOSITORY', self.local_repo_path)) |
|
181 |
||
182 |
||
28.6.1
by Stuart Bishop
Sketching out test framework |
183 |
class PostgreSQLCharmTestCase(testtools.TestCase, fixtures.TestWithFixtures): |
184 |
||
185 |
def setUp(self): |
|
186 |
super(PostgreSQLCharmTestCase, self).setUp() |
|
28.6.11
by Stuart Bishop
WIP |
187 |
|
46.2.13
by Stuart Bishop
Smarter deploy to save a few mins startup overhead |
188 |
self.juju = self.useFixture(JujuFixture()) |
28.6.13
by Stuart Bishop
Reset juju environment between tests |
189 |
|
28.6.11
by Stuart Bishop
WIP |
190 |
## Disabled until postgresql-psql is in the charm store.
|
191 |
## Otherwise, we need to make the local:postgresql-psql charm
|
|
192 |
## discoverable.
|
|
193 |
## self.useFixture(LocalCharmRepositoryFixture())
|
|
194 |
||
195 |
# If the charms fail, we don't want tests to hang indefinitely.
|
|
196 |
# We might need to increase this in some environments or if the
|
|
197 |
# environment doesn't have enough machines warmed up.
|
|
28.4.13
by Stuart Bishop
Failover tests, work in progress |
198 |
timeout = int(os.environ.get('TEST_TIMEOUT', 900)) |
28.6.12
by Stuart Bishop
Basic test working |
199 |
self.useFixture(fixtures.Timeout(timeout, gentle=True)) |
28.6.11
by Stuart Bishop
WIP |
200 |
|
46.5.27
by Stuart Bishop
WIP |
201 |
def sql(self, sql, postgres_unit=None, psql_unit=None, dbname=None): |
28.6.11
by Stuart Bishop
WIP |
202 |
'''Run some SQL on postgres_unit from psql_unit.
|
203 |
||
204 |
Uses a random psql_unit and postgres_unit if not specified.
|
|
205 |
||
28.4.13
by Stuart Bishop
Failover tests, work in progress |
206 |
postgres_unit may be set to an explicit unit name, 'master' or
|
207 |
'hot standby'.
|
|
208 |
||
28.6.11
by Stuart Bishop
WIP |
209 |
A db-admin relation is used if dbname is specified. Otherwise,
|
210 |
a standard db relation is used.
|
|
211 |
'''
|
|
212 |
if psql_unit is None: |
|
213 |
psql_unit = ( |
|
214 |
self.juju.status['services']['psql']['units'].keys()[0]) |
|
215 |
||
28.6.12
by Stuart Bishop
Basic test working |
216 |
# The psql statements we are going to execute.
|
217 |
sql = sql.strip() |
|
218 |
if not sql.endswith(';'): |
|
219 |
sql += ';' |
|
220 |
sql += '\n\\q\n' |
|
221 |
||
28.6.11
by Stuart Bishop
WIP |
222 |
# The command we run to connect psql to the desired database.
|
223 |
if postgres_unit is None: |
|
224 |
postgres_unit = ( |
|
225 |
self.juju.status['services']['postgresql']['units'].keys()[0]) |
|
28.4.13
by Stuart Bishop
Failover tests, work in progress |
226 |
elif postgres_unit == 'hot standby': |
46.5.14
by Stuart Bishop
Fixing replication, WIP |
227 |
postgres_unit = 'hot-standby' # Munge for generating script name. |
28.6.11
by Stuart Bishop
WIP |
228 |
if dbname is None: |
28.6.12
by Stuart Bishop
Basic test working |
229 |
psql_cmd = [ |
46.2.11
by Stuart Bishop
Get tests infrastructure running with juju 1.11 |
230 |
'psql-db-{}'.format(postgres_unit.replace('/', '-'))] |
28.6.11
by Stuart Bishop
WIP |
231 |
else: |
232 |
psql_cmd = [ |
|
46.2.11
by Stuart Bishop
Get tests infrastructure running with juju 1.11 |
233 |
'psql-db-admin-{}'.format( |
28.6.12
by Stuart Bishop
Basic test working |
234 |
postgres_unit.replace('/', '-')), '-d', dbname] |
28.6.11
by Stuart Bishop
WIP |
235 |
psql_args = [ |
236 |
'--quiet', '--tuples-only', '--no-align', '--no-password', |
|
237 |
'--field-separator=,', '--file=-'] |
|
46.2.11
by Stuart Bishop
Get tests infrastructure running with juju 1.11 |
238 |
cmd = [ |
239 |
'juju', 'ssh', psql_unit, |
|
240 |
# Due to Bug #1191079, we need to send the whole remote command
|
|
241 |
# as a single argument.
|
|
242 |
' '.join(psql_cmd + psql_args)] |
|
46.5.27
by Stuart Bishop
WIP |
243 |
DEBUG("SQL {}".format(sql)) |
28.6.11
by Stuart Bishop
WIP |
244 |
out = _run(self, cmd, input=sql) |
46.5.27
by Stuart Bishop
WIP |
245 |
DEBUG("OUT {}".format(out)) |
28.6.12
by Stuart Bishop
Basic test working |
246 |
result = [line.split(',') for line in out.splitlines()] |
247 |
self.addDetail('sql', text_content(repr((sql, result)))) |
|
248 |
return result |
|
28.6.8
by Stuart Bishop
WIP |
249 |
|
46.5.29
by Stuart Bishop
Failover test passing |
250 |
def pg_ctlcluster(self, unit, command): |
251 |
cmd = ['juju', 'ssh', unit, |
|
252 |
# Due to Bug #1191079, we need to send the whole remote command
|
|
253 |
# as a single argument.
|
|
254 |
'sudo pg_ctlcluster 9.1 main -force {}'.format(command)] |
|
255 |
_run(self, cmd) |
|
256 |
||
28.6.2
by Stuart Bishop
Define local repository |
257 |
def test_basic(self): |
46.7.2
by Stuart Bishop
Fixes |
258 |
'''Connect to a a single unit service via the db relationship.'''
|
46.2.13
by Stuart Bishop
Smarter deploy to save a few mins startup overhead |
259 |
self.juju.deploy(TEST_CHARM, 'postgresql') |
260 |
self.juju.deploy(PSQL_CHARM, 'psql') |
|
28.6.8
by Stuart Bishop
WIP |
261 |
self.juju.do(['add-relation', 'postgresql:db', 'psql:db']) |
262 |
self.juju.wait_until_ready() |
|
28.6.12
by Stuart Bishop
Basic test working |
263 |
|
264 |
# There a race condition here, as hooks may still be running
|
|
265 |
# from adding the relation. I'm protected here as 'juju status'
|
|
266 |
# takes about 25 seconds to run from here to my test cloud but
|
|
267 |
# others might not be so 'lucky'.
|
|
28.6.11
by Stuart Bishop
WIP |
268 |
result = self.sql('SELECT TRUE') |
28.6.12
by Stuart Bishop
Basic test working |
269 |
self.assertEqual(result, [['t']]) |
28.6.1
by Stuart Bishop
Sketching out test framework |
270 |
|
46.7.2
by Stuart Bishop
Fixes |
271 |
def test_basic_admin(self): |
272 |
'''Connect to a single unit service via the db-admin relationship.'''
|
|
273 |
self.juju.deploy(TEST_CHARM, 'postgresql') |
|
274 |
self.juju.deploy(PSQL_CHARM, 'psql') |
|
275 |
self.juju.do(['add-relation', 'postgresql:db-admin', 'psql:db-admin']) |
|
276 |
self.juju.wait_until_ready() |
|
277 |
||
278 |
result = self.sql('SELECT TRUE', dbname='postgres') |
|
279 |
self.assertEqual(result, [['t']]) |
|
280 |
||
281 |
||
46.5.28
by Stuart Bishop
WIP |
282 |
def is_master(self, postgres_unit, dbname=None): |
46.5.29
by Stuart Bishop
Failover test passing |
283 |
is_master = self.sql( |
284 |
'SELECT NOT pg_is_in_recovery()', |
|
285 |
postgres_unit, dbname=dbname)[0][0] |
|
286 |
return (is_master == 't') |
|
28.4.13
by Stuart Bishop
Failover tests, work in progress |
287 |
|
288 |
def test_failover(self): |
|
46.5.14
by Stuart Bishop
Fixing replication, WIP |
289 |
"""Set up a multi-unit service and perform failovers."""
|
46.5.29
by Stuart Bishop
Failover test passing |
290 |
self.juju.deploy(TEST_CHARM, 'postgresql', num_units=3) |
46.5.14
by Stuart Bishop
Fixing replication, WIP |
291 |
self.juju.deploy(PSQL_CHARM, 'psql') |
46.5.29
by Stuart Bishop
Failover test passing |
292 |
self.juju.do(['add-relation', 'postgresql:db', 'psql:db']) |
28.4.13
by Stuart Bishop
Failover tests, work in progress |
293 |
self.juju.wait_until_ready() |
294 |
||
46.5.29
by Stuart Bishop
Failover test passing |
295 |
# On a freshly setup service, lowest numbered unit is always the
|
296 |
# master.
|
|
28.4.13
by Stuart Bishop
Failover tests, work in progress |
297 |
units = unit_sorted( |
298 |
self.juju.status['services']['postgresql']['units'].keys()) |
|
46.5.29
by Stuart Bishop
Failover test passing |
299 |
master_unit, standby_unit_1, standby_unit_2 = units |
300 |
||
301 |
self.assertIs(True, self.is_master(master_unit)) |
|
302 |
self.assertIs(False, self.is_master(standby_unit_1)) |
|
303 |
self.assertIs(False, self.is_master(standby_unit_2)) |
|
304 |
||
305 |
self.sql('CREATE TABLE Token (x int)', master_unit) |
|
306 |
||
307 |
# Some simple helper to send data via the master and check if it
|
|
308 |
# was replicated to the hot standbys.
|
|
46.5.27
by Stuart Bishop
WIP |
309 |
_counter = [0] |
310 |
||
311 |
def send_token(unit): |
|
312 |
_counter[0] += 1 |
|
46.5.29
by Stuart Bishop
Failover test passing |
313 |
self.sql("INSERT INTO Token VALUES (%d)" % _counter[0], unit) |
46.5.27
by Stuart Bishop
WIP |
314 |
|
315 |
def token_received(unit): |
|
46.5.30
by Stuart Bishop
Test fixes |
316 |
# async replocation can lag, so retry for a little while to
|
317 |
# give the databases a chance to get their act together.
|
|
318 |
start = time.time() |
|
319 |
timeout = start + 60 |
|
320 |
while time.time() <= timeout: |
|
321 |
r = self.sql( |
|
322 |
"SELECT TRUE FROM Token WHERE x=%d" % _counter[0], unit) |
|
323 |
if r == [['t']]: |
|
324 |
return True |
|
325 |
return False |
|
46.5.27
by Stuart Bishop
WIP |
326 |
|
327 |
# Confirm that replication is actually happening.
|
|
328 |
send_token(master_unit) |
|
329 |
self.assertIs(True, token_received(standby_unit_1)) |
|
330 |
self.assertIs(True, token_received(standby_unit_2)) |
|
331 |
||
28.4.13
by Stuart Bishop
Failover tests, work in progress |
332 |
# Remove the master unit.
|
333 |
self.juju.do(['remove-unit', master_unit]) |
|
334 |
self.juju.wait_until_ready() |
|
46.5.27
by Stuart Bishop
WIP |
335 |
|
46.5.29
by Stuart Bishop
Failover test passing |
336 |
# When we failover, the unit that has received the most WAL
|
337 |
# information from the old master (most in sync) is elected the
|
|
338 |
# new master.
|
|
339 |
standby_unit_1_is_master = self.is_master(standby_unit_1) |
|
340 |
standby_unit_2_is_master = self.is_master(standby_unit_2) |
|
341 |
self.assertNotEqual( |
|
342 |
standby_unit_1_is_master, standby_unit_2_is_master) |
|
46.5.27
by Stuart Bishop
WIP |
343 |
|
344 |
if standby_unit_1_is_master: |
|
345 |
master_unit = standby_unit_1 |
|
46.5.29
by Stuart Bishop
Failover test passing |
346 |
standby_unit = standby_unit_2 |
46.5.27
by Stuart Bishop
WIP |
347 |
else: |
46.5.29
by Stuart Bishop
Failover test passing |
348 |
master_unit = standby_unit_2 |
46.5.27
by Stuart Bishop
WIP |
349 |
standby_unit = standby_unit_1 |
350 |
||
46.5.29
by Stuart Bishop
Failover test passing |
351 |
# Confirm replication is still working.
|
46.5.27
by Stuart Bishop
WIP |
352 |
send_token(master_unit) |
353 |
self.assertIs(True, token_received(standby_unit)) |
|
354 |
||
46.5.29
by Stuart Bishop
Failover test passing |
355 |
# Remove the master again, leaving a single unit.
|
356 |
self.juju.do(['remove-unit', master_unit]) |
|
357 |
self.juju.wait_until_ready() |
|
358 |
||
359 |
# Last unit is a working, standalone database.
|
|
360 |
self.is_master(standby_unit) |
|
361 |
send_token(standby_unit) |
|
362 |
||
363 |
# We can tell it is correctly reporting that it is standalone by
|
|
364 |
# seeing if the -master and -hot-standby scripts no longer exist
|
|
365 |
# on the psql unit.
|
|
366 |
self.assertRaises( |
|
367 |
subprocess.CalledProcessError, |
|
368 |
self.sql, 'SELECT TRUE', 'master') |
|
369 |
self.assertRaises( |
|
370 |
subprocess.CalledProcessError, |
|
371 |
self.sql, 'SELECT TRUE', 'hot standby') |
|
372 |
||
373 |
def test_failover_election(self): |
|
374 |
"""Ensure master elected in a failover is the best choice"""
|
|
375 |
self.juju.deploy(TEST_CHARM, 'postgresql', num_units=3) |
|
376 |
self.juju.deploy(PSQL_CHARM, 'psql') |
|
377 |
self.juju.do(['add-relation', 'postgresql:db-admin', 'psql:db-admin']) |
|
378 |
self.juju.wait_until_ready() |
|
379 |
||
380 |
# On a freshly setup service, lowest numbered unit is always the
|
|
381 |
# master.
|
|
382 |
units = unit_sorted( |
|
383 |
self.juju.status['services']['postgresql']['units'].keys()) |
|
384 |
master_unit, standby_unit_1, standby_unit_2 = units |
|
385 |
||
386 |
# Shutdown PostgreSQL on standby_unit_1 and ensure
|
|
387 |
# standby_unit_2 will have received more WAL information from
|
|
388 |
# the master.
|
|
46.5.30
by Stuart Bishop
Test fixes |
389 |
self.pg_ctlcluster(standby_unit_1, 'stop') |
46.5.29
by Stuart Bishop
Failover test passing |
390 |
self.sql("SELECT pg_switch_xlog()", master_unit, dbname='postgres') |
391 |
||
46.5.32
by Stuart Bishop
Fix election test |
392 |
# Break replication so when we bring standby_unit_1 up, it has
|
393 |
# no way of catching up.
|
|
394 |
self.sql( |
|
395 |
"ALTER ROLE juju_replication NOREPLICATION", |
|
396 |
master_unit, dbname='postgres') |
|
397 |
self.pg_ctlcluster(master_unit, 'restart') |
|
46.5.29
by Stuart Bishop
Failover test passing |
398 |
|
46.5.32
by Stuart Bishop
Fix election test |
399 |
# Restart standby_unit_1 now it has no way or resyncing.
|
46.5.30
by Stuart Bishop
Test fixes |
400 |
self.pg_ctlcluster(standby_unit_1, 'start') |
46.5.29
by Stuart Bishop
Failover test passing |
401 |
|
46.5.32
by Stuart Bishop
Fix election test |
402 |
# Failover.
|
46.5.29
by Stuart Bishop
Failover test passing |
403 |
self.juju.do(['remove-unit', master_unit]) |
404 |
self.juju.wait_until_ready() |
|
405 |
||
46.5.32
by Stuart Bishop
Fix election test |
406 |
# Fix replication.
|
407 |
self.sql( |
|
408 |
"ALTER ROLE juju_replication REPLICATION", |
|
409 |
standby_unit_2, dbname='postgres') |
|
410 |
||
46.5.29
by Stuart Bishop
Failover test passing |
411 |
# Ensure the election went as predicted.
|
46.5.32
by Stuart Bishop
Fix election test |
412 |
self.assertIs(True, self.is_master(standby_unit_2, 'postgres')) |
413 |
self.assertIs(False, self.is_master(standby_unit_1, 'postgres')) |
|
46.5.15
by Stuart Bishop
WIP |
414 |
|
28.4.13
by Stuart Bishop
Failover tests, work in progress |
415 |
|
416 |
def unit_sorted(units): |
|
417 |
"""Return a correctly sorted list of unit names."""
|
|
418 |
return sorted( |
|
419 |
units, lambda a,b: |
|
420 |
cmp(int(a.split('/')[-1]), int(b.split('/')[-1]))) |
|
28.6.1
by Stuart Bishop
Sketching out test framework |
421 |
|
46.5.14
by Stuart Bishop
Fixing replication, WIP |
422 |
|
28.6.1
by Stuart Bishop
Sketching out test framework |
423 |
if __name__ == '__main__': |
28.6.2
by Stuart Bishop
Define local repository |
424 |
raise SystemExit(unittest.main()) |