Merge lp:~bjornt/landscape-charm/package-upload-leader-change into lp:~landscape/landscape-charm/trunk
- package-upload-leader-change
- Merge into trunk
Status: | Superseded | ||||
---|---|---|---|---|---|
Proposed branch: | lp:~bjornt/landscape-charm/package-upload-leader-change | ||||
Merge into: | lp:~landscape/landscape-charm/trunk | ||||
Diff against target: |
527 lines (+206/-63) 9 files modified
charmhelpers/core/hookenv.py (+62/-1) charmhelpers/core/services/base.py (+30/-9) charmhelpers/fetch/giturl.py (+7/-5) hooks/leader-elected (+9/-0) lib/relations/haproxy.py (+5/-4) lib/relations/tests/test_haproxy.py (+61/-3) lib/services.py (+11/-20) lib/tests/stubs.py (+4/-9) lib/tests/test_services.py (+17/-12) |
||||
To merge this branch: | bzr merge lp:~bjornt/landscape-charm/package-upload-leader-change | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Landscape | Pending | ||
Landscape | Pending | ||
Review via email: mp+260933@code.launchpad.net |
Commit message
Description of the change
Don't make haproxy break when the landscape-server leader is removed.
If you would have multiple landscape-server units and the leader would
go away, the haproxy config would break. The reason was the
package-upload config was always present in the haproxy config, but the
actual backend was only configured by the leader.
I changed it so that the leader not only adds the backend, but also adds
the package-upload config that references the backend.
I had to make use of the is-leader executable, since the leader related
APIs in charmhelpers is a bit buggy. I added an is_elected_leader
function with the intention that this will be ported to charmhelpers, so
I made it accept the same parameters as the existing is_elected_leader
function.
- 303. By Björn Tillenius
-
Merge lp:~free.ekanayaka/landscape-charm/new-data-provider-flow, resolve conflicts..
- 304. By Björn Tillenius
-
Use is_leader() from charmhelpers.
- 305. By Björn Tillenius
-
Remove custom is_leader function.
- 306. By Björn Tillenius
-
Remove ClusterStub.
- 307. By Björn Tillenius
-
Merge trunk.
- 308. By Björn Tillenius
-
Add comment.
- 309. By Björn Tillenius
-
Remove is_leader parameter.
- 310. By Björn Tillenius
-
Remove unused method.
Unmerged revisions
Preview Diff
1 | === modified file 'charmhelpers/core/hookenv.py' |
2 | --- charmhelpers/core/hookenv.py 2015-05-19 09:33:01 +0000 |
3 | +++ charmhelpers/core/hookenv.py 2015-06-03 12:30:44 +0000 |
4 | @@ -364,11 +364,16 @@ |
5 | relation_settings = relation_settings if relation_settings else {} |
6 | relation_cmd_line = ['relation-set'] |
7 | accepts_file = "--file" in subprocess.check_output( |
8 | - relation_cmd_line + ["--help"]) |
9 | + relation_cmd_line + ["--help"], universal_newlines=True) |
10 | if relation_id is not None: |
11 | relation_cmd_line.extend(('-r', relation_id)) |
12 | settings = relation_settings.copy() |
13 | settings.update(kwargs) |
14 | + for key, value in settings.items(): |
15 | + # Force value to be a string: it always should, but some call |
16 | + # sites pass in things like dicts or numbers. |
17 | + if value is not None: |
18 | + settings[key] = "{}".format(value) |
19 | if accepts_file: |
20 | # --file was introduced in Juju 1.23.2. Use it by default if |
21 | # available, since otherwise we'll break if the relation data is |
22 | @@ -390,6 +395,17 @@ |
23 | flush(local_unit()) |
24 | |
25 | |
26 | +def relation_clear(r_id=None): |
27 | + ''' Clears any relation data already set on relation r_id ''' |
28 | + settings = relation_get(rid=r_id, |
29 | + unit=local_unit()) |
30 | + for setting in settings: |
31 | + if setting not in ['public-address', 'private-address']: |
32 | + settings[setting] = None |
33 | + relation_set(relation_id=r_id, |
34 | + **settings) |
35 | + |
36 | + |
37 | @cached |
38 | def relation_ids(reltype=None): |
39 | """A list of relation_ids""" |
40 | @@ -681,3 +697,48 @@ |
41 | return 'unknown' |
42 | else: |
43 | raise |
44 | + |
45 | + |
46 | +def translate_exc(from_exc, to_exc): |
47 | + def inner_translate_exc1(f): |
48 | + def inner_translate_exc2(*args, **kwargs): |
49 | + try: |
50 | + return f(*args, **kwargs) |
51 | + except from_exc: |
52 | + raise to_exc |
53 | + |
54 | + return inner_translate_exc2 |
55 | + |
56 | + return inner_translate_exc1 |
57 | + |
58 | + |
59 | +@translate_exc(from_exc=OSError, to_exc=NotImplementedError) |
60 | +def is_leader(): |
61 | + """Does the current unit hold the juju leadership |
62 | + |
63 | + Uses juju to determine whether the current unit is the leader of its peers |
64 | + """ |
65 | + cmd = ['is-leader', '--format=json'] |
66 | + return json.loads(subprocess.check_output(cmd).decode('UTF-8')) |
67 | + |
68 | + |
69 | +@translate_exc(from_exc=OSError, to_exc=NotImplementedError) |
70 | +def leader_get(attribute=None): |
71 | + """Juju leader get value(s)""" |
72 | + cmd = ['leader-get', '--format=json'] + [attribute or '-'] |
73 | + return json.loads(subprocess.check_output(cmd).decode('UTF-8')) |
74 | + |
75 | + |
76 | +@translate_exc(from_exc=OSError, to_exc=NotImplementedError) |
77 | +def leader_set(settings=None, **kwargs): |
78 | + """Juju leader set value(s)""" |
79 | + log("Juju leader-set '%s'" % (settings), level=DEBUG) |
80 | + cmd = ['leader-set'] |
81 | + settings = settings or {} |
82 | + settings.update(kwargs) |
83 | + for k, v in settings.iteritems(): |
84 | + if v is None: |
85 | + cmd.append('{}='.format(k)) |
86 | + else: |
87 | + cmd.append('{}={}'.format(k, v)) |
88 | + subprocess.check_call(cmd) |
89 | |
90 | === modified file 'charmhelpers/core/services/base.py' |
91 | --- charmhelpers/core/services/base.py 2015-05-12 14:31:50 +0000 |
92 | +++ charmhelpers/core/services/base.py 2015-06-03 12:30:44 +0000 |
93 | @@ -15,8 +15,8 @@ |
94 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
95 | |
96 | import os |
97 | -import re |
98 | import json |
99 | +from inspect import getargspec |
100 | from collections import Iterable, OrderedDict |
101 | |
102 | from charmhelpers.core import host |
103 | @@ -132,8 +132,8 @@ |
104 | if hook_name == 'stop': |
105 | self.stop_services() |
106 | else: |
107 | + self.reconfigure_services() |
108 | self.provide_data() |
109 | - self.reconfigure_services() |
110 | cfg = hookenv.config() |
111 | if cfg.implicit_save: |
112 | cfg.save() |
113 | @@ -145,15 +145,36 @@ |
114 | A provider must have a `name` attribute, which indicates which relation |
115 | to set data on, and a `provide_data()` method, which returns a dict of |
116 | data to set. |
117 | + |
118 | + The `provide_data()` method can optionally accept two parameters: |
119 | + |
120 | + * ``remote_service`` The name of the remote service that the data will |
121 | + be provided to. The `provide_data()` method will be called once |
122 | + for each connected service (not unit). This allows the method to |
123 | + tailor its data to the given service. |
124 | + * ``service_ready`` Whether or not the service definition had all of |
125 | + its requirements met, and thus the ``data_ready`` callbacks run. |
126 | + |
127 | + Note that the ``provided_data`` methods are now called **after** the |
128 | + ``data_ready`` callbacks are run. This gives the ``data_ready`` callbacks |
129 | + a chance to generate any data necessary for the providing to the remote |
130 | + services. |
131 | """ |
132 | - hook_name = hookenv.hook_name() |
133 | - for service in self.services.values(): |
134 | + for service_name, service in self.services.items(): |
135 | + service_ready = self.is_ready(service_name) |
136 | for provider in service.get('provided_data', []): |
137 | - if re.match(r'{}-relation-(joined|changed)'.format(provider.name), hook_name): |
138 | - data = provider.provide_data() |
139 | - _ready = provider._is_ready(data) if hasattr(provider, '_is_ready') else data |
140 | - if _ready: |
141 | - hookenv.relation_set(None, data) |
142 | + for relid in hookenv.relation_ids(provider.name): |
143 | + units = hookenv.related_units(relid) |
144 | + if not units: |
145 | + continue |
146 | + remote_service = units[0].split('/')[0] |
147 | + argspec = getargspec(provider.provide_data) |
148 | + if len(argspec.args) > 1: |
149 | + data = provider.provide_data(remote_service, service_ready) |
150 | + else: |
151 | + data = provider.provide_data() |
152 | + if data: |
153 | + hookenv.relation_set(relid, data) |
154 | |
155 | def reconfigure_services(self, *service_names): |
156 | """ |
157 | |
158 | === modified file 'charmhelpers/fetch/giturl.py' |
159 | --- charmhelpers/fetch/giturl.py 2015-03-12 11:42:26 +0000 |
160 | +++ charmhelpers/fetch/giturl.py 2015-06-03 12:30:44 +0000 |
161 | @@ -45,14 +45,16 @@ |
162 | else: |
163 | return True |
164 | |
165 | - def clone(self, source, dest, branch): |
166 | + def clone(self, source, dest, branch, depth=None): |
167 | if not self.can_handle(source): |
168 | raise UnhandledSource("Cannot handle {}".format(source)) |
169 | |
170 | - repo = Repo.clone_from(source, dest) |
171 | - repo.git.checkout(branch) |
172 | + if depth: |
173 | + Repo.clone_from(source, dest, branch=branch, depth=depth) |
174 | + else: |
175 | + Repo.clone_from(source, dest, branch=branch) |
176 | |
177 | - def install(self, source, branch="master", dest=None): |
178 | + def install(self, source, branch="master", dest=None, depth=None): |
179 | url_parts = self.parse_url(source) |
180 | branch_name = url_parts.path.strip("/").split("/")[-1] |
181 | if dest: |
182 | @@ -63,7 +65,7 @@ |
183 | if not os.path.exists(dest_dir): |
184 | mkdir(dest_dir, perms=0o755) |
185 | try: |
186 | - self.clone(source, dest_dir, branch) |
187 | + self.clone(source, dest_dir, branch, depth) |
188 | except GitCommandError as e: |
189 | raise UnhandledSource(e.message) |
190 | except OSError as e: |
191 | |
192 | === added file 'hooks/leader-elected' |
193 | --- hooks/leader-elected 1970-01-01 00:00:00 +0000 |
194 | +++ hooks/leader-elected 2015-06-03 12:30:44 +0000 |
195 | @@ -0,0 +1,9 @@ |
196 | +#!/usr/bin/python |
197 | +import sys |
198 | + |
199 | +from lib.services import ServicesHook |
200 | + |
201 | + |
202 | +if __name__ == "__main__": |
203 | + hook = ServicesHook() |
204 | + sys.exit(hook()) |
205 | |
206 | === modified file 'lib/relations/haproxy.py' |
207 | --- lib/relations/haproxy.py 2015-05-25 09:11:59 +0000 |
208 | +++ lib/relations/haproxy.py 2015-06-03 12:30:44 +0000 |
209 | @@ -37,10 +37,8 @@ |
210 | "http-request set-header X-Forwarded-Proto https", |
211 | "acl message path_beg -i /message-system", |
212 | "acl api path_beg -i /api", |
213 | - "acl package-upload path_beg -i /upload", |
214 | "use_backend landscape-message if message", |
215 | "use_backend landscape-api if api", |
216 | - "use_backend landscape-package-upload if package-upload", |
217 | ], |
218 | } |
219 | SERVER_BASE_PORTS = { |
220 | @@ -100,6 +98,7 @@ |
221 | def _get_https(self): |
222 | """Return the service configuration for the HTTPS frontend.""" |
223 | |
224 | + service = self._get_service("https") |
225 | backends = [ |
226 | self._get_backend("message", self._get_servers("message-server")), |
227 | self._get_backend("api", self._get_servers("api")), |
228 | @@ -108,6 +107,9 @@ |
229 | self._hookenv.log( |
230 | "This unit is the juju leader: Writing package-upload backends" |
231 | " entry.") |
232 | + service["service_options"].extend([ |
233 | + "acl package-upload path_beg -i /upload", |
234 | + "use_backend landscape-package-upload if package-upload"]) |
235 | backends.append( |
236 | self._get_backend( |
237 | "package-upload", self._get_servers("package-upload"))) |
238 | @@ -116,7 +118,6 @@ |
239 | "This unit is not the juju leader: not writing package-upload" |
240 | " backends entry.") |
241 | |
242 | - service = self._get_service("https") |
243 | service.update({ |
244 | "crts": self._get_ssl_certificate(), |
245 | "servers": self._get_servers("appserver"), |
246 | @@ -136,7 +137,7 @@ |
247 | "service_name": "landscape-%s" % name, |
248 | "service_host": "0.0.0.0", |
249 | "service_port": SERVICE_PORTS[name], |
250 | - "service_options": SERVICE_OPTIONS[name], |
251 | + "service_options": SERVICE_OPTIONS[name][:], |
252 | "errorfiles": self._get_error_files() |
253 | } |
254 | |
255 | |
256 | === modified file 'lib/relations/tests/test_haproxy.py' |
257 | --- lib/relations/tests/test_haproxy.py 2015-06-02 09:21:44 +0000 |
258 | +++ lib/relations/tests/test_haproxy.py 2015-06-03 12:30:44 +0000 |
259 | @@ -96,10 +96,8 @@ |
260 | "http-request set-header X-Forwarded-Proto https", |
261 | "acl message path_beg -i /message-system", |
262 | "acl api path_beg -i /api", |
263 | - "acl package-upload path_beg -i /upload", |
264 | "use_backend landscape-message if message", |
265 | - "use_backend landscape-api if api", |
266 | - "use_backend landscape-package-upload if package-upload"], |
267 | + "use_backend landscape-api if api"], |
268 | "errorfiles": expected_errorfiles, |
269 | "crts": expected_certs, |
270 | "servers": [ |
271 | @@ -162,6 +160,66 @@ |
272 | |
273 | self.assertRaises(HookError, provider.provide_data) |
274 | |
275 | + def test_provide_data_package_upload_leader(self): |
276 | + """ |
277 | + If the unit is a leader, package-upload config is provided for |
278 | + the https service, but not for http. |
279 | + """ |
280 | + relation = HAProxyProvider( |
281 | + SAMPLE_SERVICE_COUNT_DATA, paths=self.paths, is_leader=True) |
282 | + |
283 | + data = relation.provide_data() |
284 | + |
285 | + [http, https] = yaml.safe_load(data["services"]) |
286 | + self.assertNotIn( |
287 | + "acl package-upload path_beg -i /upload", |
288 | + http["service_options"]) |
289 | + self.assertNotIn( |
290 | + "use_backend landscape-package-upload if package-upload", |
291 | + http["service_options"]) |
292 | + self.assertNotIn( |
293 | + "landscape-package-upload", |
294 | + [backend["backend_name"] for backend in http["backends"]]) |
295 | + self.assertIn( |
296 | + "acl package-upload path_beg -i /upload", |
297 | + https["service_options"]) |
298 | + self.assertIn( |
299 | + "use_backend landscape-package-upload if package-upload", |
300 | + https["service_options"]) |
301 | + self.assertIn( |
302 | + "landscape-package-upload", |
303 | + [backend["backend_name"] for backend in https["backends"]]) |
304 | + |
305 | + def test_provide_data_package_upload_no_leader(self): |
306 | + """ |
307 | + If the unit is not a leader, package-upload config isn't |
308 | + provided for neither the https nor http services. |
309 | + """ |
310 | + relation = HAProxyProvider( |
311 | + SAMPLE_SERVICE_COUNT_DATA, paths=self.paths, is_leader=False) |
312 | + |
313 | + data = relation.provide_data() |
314 | + |
315 | + [http, https] = yaml.safe_load(data["services"]) |
316 | + self.assertNotIn( |
317 | + "acl package-upload path_beg -i /upload", |
318 | + http["service_options"]) |
319 | + self.assertNotIn( |
320 | + "use_backend landscape-package-upload if package-upload", |
321 | + http["service_options"]) |
322 | + self.assertNotIn( |
323 | + "landscape-package-upload", |
324 | + [backend["backend_name"] for backend in http["backends"]]) |
325 | + self.assertNotIn( |
326 | + "acl package-upload path_beg -i /upload", |
327 | + https["service_options"]) |
328 | + self.assertNotIn( |
329 | + "use_backend landscape-package-upload if package-upload", |
330 | + https["service_options"]) |
331 | + self.assertNotIn( |
332 | + "landscape-package-upload", |
333 | + [backend["backend_name"] for backend in https["backends"]]) |
334 | + |
335 | def test_default_ssl_cert_is_used_without_config_keys(self): |
336 | """ |
337 | If no "ssl-cert" is specified, the provide_data method returns |
338 | |
339 | === modified file 'lib/services.py' |
340 | --- lib/services.py 2015-06-02 09:21:44 +0000 |
341 | +++ lib/services.py 2015-06-03 12:30:44 +0000 |
342 | @@ -5,7 +5,6 @@ |
343 | from charmhelpers.core import host |
344 | from charmhelpers.core.services.base import ServiceManager |
345 | from charmhelpers.core.services.helpers import render_template |
346 | -from charmhelpers.contrib.hahelpers import cluster |
347 | |
348 | from lib.hook import Hook |
349 | from lib.paths import default_paths |
350 | @@ -36,11 +35,10 @@ |
351 | all relation data we need in order to configure this Landscape unit, and |
352 | proceed with the configuration if ready. |
353 | """ |
354 | - def __init__(self, hookenv=hookenv, cluster=cluster, host=host, |
355 | + def __init__(self, hookenv=hookenv, host=host, |
356 | subprocess=subprocess, paths=default_paths, fetch=fetch): |
357 | super(ServicesHook, self).__init__(hookenv=hookenv) |
358 | self._hookenv = hookenv |
359 | - self._cluster = cluster |
360 | self._host = host |
361 | self._paths = paths |
362 | self._subprocess = subprocess |
363 | @@ -48,20 +46,18 @@ |
364 | |
365 | def _run(self): |
366 | leader_context = None |
367 | - is_leader = self._cluster.is_elected_leader(None) |
368 | + is_leader = self._hookenv.is_leader() |
369 | if is_leader: |
370 | leader_context = LandscapeLeaderContext( |
371 | host=self._host, hookenv=self._hookenv) |
372 | |
373 | - haproxy_provider = HAProxyProvider( |
374 | - SERVICE_COUNTS, paths=self._paths, is_leader=is_leader) |
375 | - |
376 | manager = ServiceManager(services=[{ |
377 | "service": "landscape", |
378 | "ports": [], |
379 | "provided_data": [ |
380 | LandscapeProvider(leader_context), |
381 | - haproxy_provider, |
382 | + HAProxyProvider( |
383 | + SERVICE_COUNTS, paths=self._paths, is_leader=is_leader), |
384 | RabbitMQProvider(), |
385 | ], |
386 | # Required data is available to the render_template calls below. |
387 | @@ -94,16 +90,11 @@ |
388 | "start": LSCtl(subprocess=self._subprocess, hookenv=self._hookenv), |
389 | }]) |
390 | |
391 | - # XXX The services framework only triggers data providers within the |
392 | - # context of relation joined/changed hooks, however we also |
393 | - # want to trigger the haproxy provider if the SSL certificate |
394 | - # has changed. |
395 | - if self._hookenv.hook_name() == "config-changed": |
396 | - config = self._hookenv.config() |
397 | - if config.changed("ssl-cert") or config.changed("ssl-key"): |
398 | - relation_ids = self._hookenv.relation_ids(HAProxyProvider.name) |
399 | - data = haproxy_provider.provide_data() |
400 | - for relation_id in relation_ids: |
401 | - self._hookenv.relation_set(relation_id, data) |
402 | - |
403 | manager.manage() |
404 | + |
405 | + def _set_haproxy_data(self, haproxy_provider): |
406 | + """Provide and set the data in the haproxy relation.""" |
407 | + relation_ids = self._hookenv.relation_ids(HAProxyProvider.name) |
408 | + data = haproxy_provider.provide_data() |
409 | + for relation_id in relation_ids: |
410 | + self._hookenv.relation_set(relation_id, data) |
411 | |
412 | === modified file 'lib/tests/stubs.py' |
413 | --- lib/tests/stubs.py 2015-06-02 07:06:30 +0000 |
414 | +++ lib/tests/stubs.py 2015-06-03 12:30:44 +0000 |
415 | @@ -10,6 +10,7 @@ |
416 | hook = "some-hook" |
417 | unit = "landscape-server/0" |
418 | relid = None |
419 | + leader = True |
420 | |
421 | def __init__(self, charm_dir): |
422 | self.messages = [] |
423 | @@ -68,6 +69,9 @@ |
424 | def action_fail(self, message): |
425 | self._action_fails.append(message) |
426 | |
427 | + def is_leader(self): |
428 | + return self.leader |
429 | + |
430 | |
431 | class FetchStub(object): |
432 | """Provide a testable stub for C{charmhelpers.fetch}.""" |
433 | @@ -92,15 +96,6 @@ |
434 | self.installed.append((packages, options, fatal)) |
435 | |
436 | |
437 | -class ClusterStub(object): |
438 | - """Testable stub for C{charmhelpers.contrib.hahelpers.cluster}.""" |
439 | - |
440 | - leader = True |
441 | - |
442 | - def is_elected_leader(self, resource): |
443 | - return self.leader |
444 | - |
445 | - |
446 | class HostStub(object): |
447 | """Testable stub for C{charmhelpers.core.host}.""" |
448 | |
449 | |
450 | === modified file 'lib/tests/test_services.py' |
451 | --- lib/tests/test_services.py 2015-05-26 12:05:02 +0000 |
452 | +++ lib/tests/test_services.py 2015-06-03 12:30:44 +0000 |
453 | @@ -5,7 +5,7 @@ |
454 | from charmhelpers.core import templating |
455 | |
456 | from lib.tests.helpers import HookenvTest |
457 | -from lib.tests.stubs import ClusterStub, HostStub, SubprocessStub, FetchStub |
458 | +from lib.tests.stubs import HostStub, SubprocessStub, FetchStub |
459 | from lib.tests.sample import ( |
460 | SAMPLE_DB_UNIT_DATA, SAMPLE_LEADER_CONTEXT_DATA, SAMPLE_AMQP_UNIT_DATA, |
461 | SAMPLE_CONFIG_LICENSE_DATA, SAMPLE_CONFIG_OPENID_DATA, SAMPLE_HOSTED_DATA, |
462 | @@ -21,7 +21,6 @@ |
463 | |
464 | def setUp(self): |
465 | super(ServicesHookTest, self).setUp() |
466 | - self.cluster = ClusterStub() |
467 | self.host = HostStub() |
468 | self.subprocess = SubprocessStub() |
469 | self.subprocess.add_fake_executable(SCHEMA_SCRIPT) |
470 | @@ -31,8 +30,8 @@ |
471 | self.root_dir = self.useFixture(RootDir()) |
472 | self.fetch = FetchStub() |
473 | self.hook = ServicesHook( |
474 | - hookenv=self.hookenv, cluster=self.cluster, host=self.host, |
475 | - subprocess=self.subprocess, paths=self.paths, fetch=self.fetch) |
476 | + hookenv=self.hookenv, host=self.host, subprocess=self.subprocess, |
477 | + paths=self.paths, fetch=self.fetch) |
478 | |
479 | # XXX Monkey patch the templating API, charmhelpers doesn't sport |
480 | # any dependency injection here as well. |
481 | @@ -77,12 +76,8 @@ |
482 | self.hook() |
483 | # Assert that the HAProxyProvider has run by checking that it set the |
484 | # relation with the dict returned by HAProxyProvider.provide_data (the |
485 | - # only key of that dict is 'services'). The ID of relation being set |
486 | - # is None because we're running in the website-relation-joined hook |
487 | - # and are using the default relation ID (which in a real-world |
488 | - # relation-set run will resolve to the relation for the http |
489 | - # interface). |
490 | - self.assertIn("services", self.hookenv.relations[None]) |
491 | + # only key of that dict is 'services'). |
492 | + self.assertIn("services", self.hookenv.relations["website:1"]) |
493 | |
494 | def test_amqp_relation_not_ready(self): |
495 | """ |
496 | @@ -179,7 +174,7 @@ |
497 | If we're not the leader unit and we didn't yet get relation data from |
498 | the leader, we are not ready. |
499 | """ |
500 | - self.cluster.leader = False |
501 | + self.hookenv.leader = False |
502 | self.hook() |
503 | self.assertIn( |
504 | ("Incomplete relation: LandscapeRequirer", "DEBUG"), |
505 | @@ -190,7 +185,7 @@ |
506 | If we're not the leader unit and we got relation data from the leader, |
507 | along with the rest of required relations, then we're good. |
508 | """ |
509 | - self.cluster.leader = False |
510 | + self.hookenv.leader = False |
511 | self.hookenv.relations.update({ |
512 | "cluster": { |
513 | "cluster:1": { |
514 | @@ -236,3 +231,13 @@ |
515 | self.hook() |
516 | data = yaml.load(self.hookenv.relations["website:1"]["services"]) |
517 | self.assertIsNotNone(data) |
518 | + |
519 | + def test_leader_elected(self): |
520 | + """ |
521 | + When a leader is elected, the ServicesHook sets the haproxy |
522 | + relation data. |
523 | + """ |
524 | + self.hookenv.hook = "leader-elected" |
525 | + self.hook() |
526 | + data = yaml.load(self.hookenv.relations["website:1"]["services"]) |
527 | + self.assertIsNotNone(data) |