Merge lp:~billy-olsen/charms/trusty/glance/public-endpoint-host into lp:~openstack-charmers-archive/charms/trusty/glance/next
- Trusty Tahr (14.04)
- public-endpoint-host
- Merge into next
Proposed by
Billy Olsen
Status: | Merged |
---|---|
Merged at revision: | 119 |
Proposed branch: | lp:~billy-olsen/charms/trusty/glance/public-endpoint-host |
Merge into: | lp:~openstack-charmers-archive/charms/trusty/glance/next |
Diff against target: |
761 lines (+321/-87) 9 files modified
config.yaml (+10/-0) hooks/charmhelpers/contrib/hahelpers/cluster.py (+25/-0) hooks/charmhelpers/contrib/openstack/ip.py (+49/-44) hooks/charmhelpers/contrib/openstack/neutron.py (+10/-5) hooks/charmhelpers/core/hookenv.py (+147/-10) hooks/charmhelpers/core/host.py (+1/-1) hooks/charmhelpers/core/services/base.py (+32/-11) hooks/charmhelpers/fetch/__init__.py (+1/-1) unit_tests/test_glance_relations.py (+46/-15) |
To merge this branch: | bzr merge lp:~billy-olsen/charms/trusty/glance/public-endpoint-host |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Corey Bryant (community) | Approve | ||
Review via email: mp+261009@code.launchpad.net |
Commit message
Description of the change
Provides a config option which allows the user to specify the public hostname used to advertise to keystone when creating endpoints.
To post a comment you must log in.
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : | # |
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #4727 glance-next for billy-olsen mp261009
UNIT OK: passed
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #4453 glance-next for billy-olsen mp261009
AMULET OK: passed
Build: http://
- 121. By Billy Olsen
-
c-h sync
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #5071 glance-next for billy-olsen mp261009
LINT OK: passed
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #4750 glance-next for billy-olsen mp261009
UNIT OK: passed
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #4478 glance-next for billy-olsen mp261009
AMULET OK: passed
Build: http://
Revision history for this message
Corey Bryant (corey.bryant) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'config.yaml' |
2 | --- config.yaml 2015-04-01 16:48:59 +0000 |
3 | +++ config.yaml 2015-06-04 23:12:46 +0000 |
4 | @@ -176,6 +176,16 @@ |
5 | 192.168.0.0/24) |
6 | . |
7 | This network will be used for public endpoints. |
8 | + os-public-hostname: |
9 | + type: string |
10 | + default: |
11 | + description: | |
12 | + The hostname or address of the public endpoints created for glance |
13 | + in the keystone identity provider. |
14 | + . |
15 | + This value will be used for public endpoints. For example, an |
16 | + os-public-hostname set to 'glance.example.com' with ssl enabled will |
17 | + create a public endpoint for glance of https://glance.example.com:9292/ |
18 | prefer-ipv6: |
19 | type: boolean |
20 | default: False |
21 | |
22 | === modified file 'hooks/charmhelpers/contrib/hahelpers/cluster.py' |
23 | --- hooks/charmhelpers/contrib/hahelpers/cluster.py 2015-03-20 17:15:02 +0000 |
24 | +++ hooks/charmhelpers/contrib/hahelpers/cluster.py 2015-06-04 23:12:46 +0000 |
25 | @@ -52,6 +52,8 @@ |
26 | bool_from_string, |
27 | ) |
28 | |
29 | +DC_RESOURCE_NAME = 'DC' |
30 | + |
31 | |
32 | class HAIncompleteConfig(Exception): |
33 | pass |
34 | @@ -95,6 +97,27 @@ |
35 | return False |
36 | |
37 | |
38 | +def is_crm_dc(): |
39 | + """ |
40 | + Determine leadership by querying the pacemaker Designated Controller |
41 | + """ |
42 | + cmd = ['crm', 'status'] |
43 | + try: |
44 | + status = subprocess.check_output(cmd, stderr=subprocess.STDOUT) |
45 | + if not isinstance(status, six.text_type): |
46 | + status = six.text_type(status, "utf-8") |
47 | + except subprocess.CalledProcessError: |
48 | + return False |
49 | + current_dc = '' |
50 | + for line in status.split('\n'): |
51 | + if line.startswith('Current DC'): |
52 | + # Current DC: juju-lytrusty-machine-2 (168108163) - partition with quorum |
53 | + current_dc = line.split(':')[1].split()[0] |
54 | + if current_dc == get_unit_hostname(): |
55 | + return True |
56 | + return False |
57 | + |
58 | + |
59 | @retry_on_exception(5, base_delay=2, exc_type=CRMResourceNotFound) |
60 | def is_crm_leader(resource, retry=False): |
61 | """ |
62 | @@ -104,6 +127,8 @@ |
63 | We allow this operation to be retried to avoid the possibility of getting a |
64 | false negative. See LP #1396246 for more info. |
65 | """ |
66 | + if resource == DC_RESOURCE_NAME: |
67 | + return is_crm_dc() |
68 | cmd = ['crm', 'resource', 'show', resource] |
69 | try: |
70 | status = subprocess.check_output(cmd, stderr=subprocess.STDOUT) |
71 | |
72 | === modified file 'hooks/charmhelpers/contrib/openstack/ip.py' |
73 | --- hooks/charmhelpers/contrib/openstack/ip.py 2015-03-20 17:15:02 +0000 |
74 | +++ hooks/charmhelpers/contrib/openstack/ip.py 2015-06-04 23:12:46 +0000 |
75 | @@ -17,6 +17,7 @@ |
76 | from charmhelpers.core.hookenv import ( |
77 | config, |
78 | unit_get, |
79 | + service_name, |
80 | ) |
81 | from charmhelpers.contrib.network.ip import ( |
82 | get_address_in_network, |
83 | @@ -26,8 +27,6 @@ |
84 | ) |
85 | from charmhelpers.contrib.hahelpers.cluster import is_clustered |
86 | |
87 | -from functools import partial |
88 | - |
89 | PUBLIC = 'public' |
90 | INTERNAL = 'int' |
91 | ADMIN = 'admin' |
92 | @@ -35,15 +34,18 @@ |
93 | ADDRESS_MAP = { |
94 | PUBLIC: { |
95 | 'config': 'os-public-network', |
96 | - 'fallback': 'public-address' |
97 | + 'fallback': 'public-address', |
98 | + 'override': 'os-public-hostname', |
99 | }, |
100 | INTERNAL: { |
101 | 'config': 'os-internal-network', |
102 | - 'fallback': 'private-address' |
103 | + 'fallback': 'private-address', |
104 | + 'override': 'os-internal-hostname', |
105 | }, |
106 | ADMIN: { |
107 | 'config': 'os-admin-network', |
108 | - 'fallback': 'private-address' |
109 | + 'fallback': 'private-address', |
110 | + 'override': 'os-admin-hostname', |
111 | } |
112 | } |
113 | |
114 | @@ -57,15 +59,50 @@ |
115 | :param endpoint_type: str endpoint type to resolve. |
116 | :param returns: str base URL for services on the current service unit. |
117 | """ |
118 | - scheme = 'http' |
119 | - if 'https' in configs.complete_contexts(): |
120 | - scheme = 'https' |
121 | + scheme = _get_scheme(configs) |
122 | + |
123 | address = resolve_address(endpoint_type) |
124 | if is_ipv6(address): |
125 | address = "[{}]".format(address) |
126 | + |
127 | return '%s://%s' % (scheme, address) |
128 | |
129 | |
130 | +def _get_scheme(configs): |
131 | + """Returns the scheme to use for the url (either http or https) |
132 | + depending upon whether https is in the configs value. |
133 | + |
134 | + :param configs: OSTemplateRenderer config templating object to inspect |
135 | + for a complete https context. |
136 | + :returns: either 'http' or 'https' depending on whether https is |
137 | + configured within the configs context. |
138 | + """ |
139 | + scheme = 'http' |
140 | + if configs and 'https' in configs.complete_contexts(): |
141 | + scheme = 'https' |
142 | + return scheme |
143 | + |
144 | + |
145 | +def _get_address_override(endpoint_type=PUBLIC): |
146 | + """Returns any address overrides that the user has defined based on the |
147 | + endpoint type. |
148 | + |
149 | + Note: this function allows for the service name to be inserted into the |
150 | + address if the user specifies {service_name}.somehost.org. |
151 | + |
152 | + :param endpoint_type: the type of endpoint to retrieve the override |
153 | + value for. |
154 | + :returns: any endpoint address or hostname that the user has overridden |
155 | + or None if an override is not present. |
156 | + """ |
157 | + override_key = ADDRESS_MAP[endpoint_type]['override'] |
158 | + addr_override = config(override_key) |
159 | + if not addr_override: |
160 | + return None |
161 | + else: |
162 | + return addr_override.format(service_name=service_name()) |
163 | + |
164 | + |
165 | def resolve_address(endpoint_type=PUBLIC): |
166 | """Return unit address depending on net config. |
167 | |
168 | @@ -77,7 +114,10 @@ |
169 | |
170 | :param endpoint_type: Network endpoing type |
171 | """ |
172 | - resolved_address = None |
173 | + resolved_address = _get_address_override(endpoint_type) |
174 | + if resolved_address: |
175 | + return resolved_address |
176 | + |
177 | vips = config('vip') |
178 | if vips: |
179 | vips = vips.split() |
180 | @@ -109,38 +149,3 @@ |
181 | "clustered=%s)" % (net_type, clustered)) |
182 | |
183 | return resolved_address |
184 | - |
185 | - |
186 | -def endpoint_url(configs, url_template, port, endpoint_type=PUBLIC, |
187 | - override=None): |
188 | - """Returns the correct endpoint URL to advertise to Keystone. |
189 | - |
190 | - This method provides the correct endpoint URL which should be advertised to |
191 | - the keystone charm for endpoint creation. This method allows for the url to |
192 | - be overridden to force a keystone endpoint to have specific URL for any of |
193 | - the defined scopes (admin, internal, public). |
194 | - |
195 | - :param configs: OSTemplateRenderer config templating object to inspect |
196 | - for a complete https context. |
197 | - :param url_template: str format string for creating the url template. Only |
198 | - two values will be passed - the scheme+hostname |
199 | - returned by the canonical_url and the port. |
200 | - :param endpoint_type: str endpoint type to resolve. |
201 | - :param override: str the name of the config option which overrides the |
202 | - endpoint URL defined by the charm itself. None will |
203 | - disable any overrides (default). |
204 | - """ |
205 | - if override: |
206 | - # Return any user-defined overrides for the keystone endpoint URL. |
207 | - user_value = config(override) |
208 | - if user_value: |
209 | - return user_value.strip() |
210 | - |
211 | - return url_template % (canonical_url(configs, endpoint_type), port) |
212 | - |
213 | - |
214 | -public_endpoint = partial(endpoint_url, endpoint_type=PUBLIC) |
215 | - |
216 | -internal_endpoint = partial(endpoint_url, endpoint_type=INTERNAL) |
217 | - |
218 | -admin_endpoint = partial(endpoint_url, endpoint_type=ADMIN) |
219 | |
220 | === modified file 'hooks/charmhelpers/contrib/openstack/neutron.py' |
221 | --- hooks/charmhelpers/contrib/openstack/neutron.py 2015-04-16 19:53:49 +0000 |
222 | +++ hooks/charmhelpers/contrib/openstack/neutron.py 2015-06-04 23:12:46 +0000 |
223 | @@ -256,11 +256,14 @@ |
224 | def parse_mappings(mappings): |
225 | parsed = {} |
226 | if mappings: |
227 | - mappings = mappings.split(' ') |
228 | + mappings = mappings.split() |
229 | for m in mappings: |
230 | p = m.partition(':') |
231 | - if p[1] == ':': |
232 | - parsed[p[0].strip()] = p[2].strip() |
233 | + key = p[0].strip() |
234 | + if p[1]: |
235 | + parsed[key] = p[2].strip() |
236 | + else: |
237 | + parsed[key] = '' |
238 | |
239 | return parsed |
240 | |
241 | @@ -283,13 +286,13 @@ |
242 | Returns dict of the form {bridge:port}. |
243 | """ |
244 | _mappings = parse_mappings(mappings) |
245 | - if not _mappings: |
246 | + if not _mappings or list(_mappings.values()) == ['']: |
247 | if not mappings: |
248 | return {} |
249 | |
250 | # For backwards-compatibility we need to support port-only provided in |
251 | # config. |
252 | - _mappings = {default_bridge: mappings.split(' ')[0]} |
253 | + _mappings = {default_bridge: mappings.split()[0]} |
254 | |
255 | bridges = _mappings.keys() |
256 | ports = _mappings.values() |
257 | @@ -309,6 +312,8 @@ |
258 | |
259 | Mappings must be a space-delimited list of provider:start:end mappings. |
260 | |
261 | + The start:end range is optional and may be omitted. |
262 | + |
263 | Returns dict of the form {provider: (start, end)}. |
264 | """ |
265 | _mappings = parse_mappings(mappings) |
266 | |
267 | === modified file 'hooks/charmhelpers/core/hookenv.py' |
268 | --- hooks/charmhelpers/core/hookenv.py 2015-04-16 19:53:49 +0000 |
269 | +++ hooks/charmhelpers/core/hookenv.py 2015-06-04 23:12:46 +0000 |
270 | @@ -21,12 +21,14 @@ |
271 | # Charm Helpers Developers <juju@lists.ubuntu.com> |
272 | |
273 | from __future__ import print_function |
274 | +from functools import wraps |
275 | import os |
276 | import json |
277 | import yaml |
278 | import subprocess |
279 | import sys |
280 | import errno |
281 | +import tempfile |
282 | from subprocess import CalledProcessError |
283 | |
284 | import six |
285 | @@ -58,15 +60,17 @@ |
286 | |
287 | will cache the result of unit_get + 'test' for future calls. |
288 | """ |
289 | + @wraps(func) |
290 | def wrapper(*args, **kwargs): |
291 | global cache |
292 | key = str((func, args, kwargs)) |
293 | try: |
294 | return cache[key] |
295 | except KeyError: |
296 | - res = func(*args, **kwargs) |
297 | - cache[key] = res |
298 | - return res |
299 | + pass # Drop out of the exception handler scope. |
300 | + res = func(*args, **kwargs) |
301 | + cache[key] = res |
302 | + return res |
303 | return wrapper |
304 | |
305 | |
306 | @@ -178,7 +182,7 @@ |
307 | |
308 | def remote_unit(): |
309 | """The remote unit for the current relation hook""" |
310 | - return os.environ['JUJU_REMOTE_UNIT'] |
311 | + return os.environ.get('JUJU_REMOTE_UNIT', None) |
312 | |
313 | |
314 | def service_name(): |
315 | @@ -250,6 +254,12 @@ |
316 | except KeyError: |
317 | return (self._prev_dict or {})[key] |
318 | |
319 | + def get(self, key, default=None): |
320 | + try: |
321 | + return self[key] |
322 | + except KeyError: |
323 | + return default |
324 | + |
325 | def keys(self): |
326 | prev_keys = [] |
327 | if self._prev_dict is not None: |
328 | @@ -353,18 +363,49 @@ |
329 | """Set relation information for the current unit""" |
330 | relation_settings = relation_settings if relation_settings else {} |
331 | relation_cmd_line = ['relation-set'] |
332 | + accepts_file = "--file" in subprocess.check_output( |
333 | + relation_cmd_line + ["--help"], universal_newlines=True) |
334 | if relation_id is not None: |
335 | relation_cmd_line.extend(('-r', relation_id)) |
336 | - for k, v in (list(relation_settings.items()) + list(kwargs.items())): |
337 | - if v is None: |
338 | - relation_cmd_line.append('{}='.format(k)) |
339 | - else: |
340 | - relation_cmd_line.append('{}={}'.format(k, v)) |
341 | - subprocess.check_call(relation_cmd_line) |
342 | + settings = relation_settings.copy() |
343 | + settings.update(kwargs) |
344 | + for key, value in settings.items(): |
345 | + # Force value to be a string: it always should, but some call |
346 | + # sites pass in things like dicts or numbers. |
347 | + if value is not None: |
348 | + settings[key] = "{}".format(value) |
349 | + if accepts_file: |
350 | + # --file was introduced in Juju 1.23.2. Use it by default if |
351 | + # available, since otherwise we'll break if the relation data is |
352 | + # too big. Ideally we should tell relation-set to read the data from |
353 | + # stdin, but that feature is broken in 1.23.2: Bug #1454678. |
354 | + with tempfile.NamedTemporaryFile(delete=False) as settings_file: |
355 | + settings_file.write(yaml.safe_dump(settings).encode("utf-8")) |
356 | + subprocess.check_call( |
357 | + relation_cmd_line + ["--file", settings_file.name]) |
358 | + os.remove(settings_file.name) |
359 | + else: |
360 | + for key, value in settings.items(): |
361 | + if value is None: |
362 | + relation_cmd_line.append('{}='.format(key)) |
363 | + else: |
364 | + relation_cmd_line.append('{}={}'.format(key, value)) |
365 | + subprocess.check_call(relation_cmd_line) |
366 | # Flush cache of any relation-gets for local unit |
367 | flush(local_unit()) |
368 | |
369 | |
370 | +def relation_clear(r_id=None): |
371 | + ''' Clears any relation data already set on relation r_id ''' |
372 | + settings = relation_get(rid=r_id, |
373 | + unit=local_unit()) |
374 | + for setting in settings: |
375 | + if setting not in ['public-address', 'private-address']: |
376 | + settings[setting] = None |
377 | + relation_set(relation_id=r_id, |
378 | + **settings) |
379 | + |
380 | + |
381 | @cached |
382 | def relation_ids(reltype=None): |
383 | """A list of relation_ids""" |
384 | @@ -509,6 +550,11 @@ |
385 | return None |
386 | |
387 | |
388 | +def unit_public_ip(): |
389 | + """Get this unit's public IP address""" |
390 | + return unit_get('public-address') |
391 | + |
392 | + |
393 | def unit_private_ip(): |
394 | """Get this unit's private IP address""" |
395 | return unit_get('private-address') |
396 | @@ -605,3 +651,94 @@ |
397 | |
398 | The results set by action_set are preserved.""" |
399 | subprocess.check_call(['action-fail', message]) |
400 | + |
401 | + |
402 | +def status_set(workload_state, message): |
403 | + """Set the workload state with a message |
404 | + |
405 | + Use status-set to set the workload state with a message which is visible |
406 | + to the user via juju status. If the status-set command is not found then |
407 | + assume this is juju < 1.23 and juju-log the message unstead. |
408 | + |
409 | + workload_state -- valid juju workload state. |
410 | + message -- status update message |
411 | + """ |
412 | + valid_states = ['maintenance', 'blocked', 'waiting', 'active'] |
413 | + if workload_state not in valid_states: |
414 | + raise ValueError( |
415 | + '{!r} is not a valid workload state'.format(workload_state) |
416 | + ) |
417 | + cmd = ['status-set', workload_state, message] |
418 | + try: |
419 | + ret = subprocess.call(cmd) |
420 | + if ret == 0: |
421 | + return |
422 | + except OSError as e: |
423 | + if e.errno != errno.ENOENT: |
424 | + raise |
425 | + log_message = 'status-set failed: {} {}'.format(workload_state, |
426 | + message) |
427 | + log(log_message, level='INFO') |
428 | + |
429 | + |
430 | +def status_get(): |
431 | + """Retrieve the previously set juju workload state |
432 | + |
433 | + If the status-set command is not found then assume this is juju < 1.23 and |
434 | + return 'unknown' |
435 | + """ |
436 | + cmd = ['status-get'] |
437 | + try: |
438 | + raw_status = subprocess.check_output(cmd, universal_newlines=True) |
439 | + status = raw_status.rstrip() |
440 | + return status |
441 | + except OSError as e: |
442 | + if e.errno == errno.ENOENT: |
443 | + return 'unknown' |
444 | + else: |
445 | + raise |
446 | + |
447 | + |
448 | +def translate_exc(from_exc, to_exc): |
449 | + def inner_translate_exc1(f): |
450 | + def inner_translate_exc2(*args, **kwargs): |
451 | + try: |
452 | + return f(*args, **kwargs) |
453 | + except from_exc: |
454 | + raise to_exc |
455 | + |
456 | + return inner_translate_exc2 |
457 | + |
458 | + return inner_translate_exc1 |
459 | + |
460 | + |
461 | +@translate_exc(from_exc=OSError, to_exc=NotImplementedError) |
462 | +def is_leader(): |
463 | + """Does the current unit hold the juju leadership |
464 | + |
465 | + Uses juju to determine whether the current unit is the leader of its peers |
466 | + """ |
467 | + cmd = ['is-leader', '--format=json'] |
468 | + return json.loads(subprocess.check_output(cmd).decode('UTF-8')) |
469 | + |
470 | + |
471 | +@translate_exc(from_exc=OSError, to_exc=NotImplementedError) |
472 | +def leader_get(attribute=None): |
473 | + """Juju leader get value(s)""" |
474 | + cmd = ['leader-get', '--format=json'] + [attribute or '-'] |
475 | + return json.loads(subprocess.check_output(cmd).decode('UTF-8')) |
476 | + |
477 | + |
478 | +@translate_exc(from_exc=OSError, to_exc=NotImplementedError) |
479 | +def leader_set(settings=None, **kwargs): |
480 | + """Juju leader set value(s)""" |
481 | + log("Juju leader-set '%s'" % (settings), level=DEBUG) |
482 | + cmd = ['leader-set'] |
483 | + settings = settings or {} |
484 | + settings.update(kwargs) |
485 | + for k, v in settings.iteritems(): |
486 | + if v is None: |
487 | + cmd.append('{}='.format(k)) |
488 | + else: |
489 | + cmd.append('{}={}'.format(k, v)) |
490 | + subprocess.check_call(cmd) |
491 | |
492 | === modified file 'hooks/charmhelpers/core/host.py' |
493 | --- hooks/charmhelpers/core/host.py 2015-03-20 17:15:02 +0000 |
494 | +++ hooks/charmhelpers/core/host.py 2015-06-04 23:12:46 +0000 |
495 | @@ -90,7 +90,7 @@ |
496 | ['service', service_name, 'status'], |
497 | stderr=subprocess.STDOUT).decode('UTF-8') |
498 | except subprocess.CalledProcessError as e: |
499 | - return 'unrecognized service' not in e.output |
500 | + return b'unrecognized service' not in e.output |
501 | else: |
502 | return True |
503 | |
504 | |
505 | === modified file 'hooks/charmhelpers/core/services/base.py' |
506 | --- hooks/charmhelpers/core/services/base.py 2015-03-20 17:15:02 +0000 |
507 | +++ hooks/charmhelpers/core/services/base.py 2015-06-04 23:12:46 +0000 |
508 | @@ -15,9 +15,9 @@ |
509 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
510 | |
511 | import os |
512 | -import re |
513 | import json |
514 | -from collections import Iterable |
515 | +from inspect import getargspec |
516 | +from collections import Iterable, OrderedDict |
517 | |
518 | from charmhelpers.core import host |
519 | from charmhelpers.core import hookenv |
520 | @@ -119,7 +119,7 @@ |
521 | """ |
522 | self._ready_file = os.path.join(hookenv.charm_dir(), 'READY-SERVICES.json') |
523 | self._ready = None |
524 | - self.services = {} |
525 | + self.services = OrderedDict() |
526 | for service in services or []: |
527 | service_name = service['service'] |
528 | self.services[service_name] = service |
529 | @@ -132,8 +132,8 @@ |
530 | if hook_name == 'stop': |
531 | self.stop_services() |
532 | else: |
533 | + self.reconfigure_services() |
534 | self.provide_data() |
535 | - self.reconfigure_services() |
536 | cfg = hookenv.config() |
537 | if cfg.implicit_save: |
538 | cfg.save() |
539 | @@ -145,15 +145,36 @@ |
540 | A provider must have a `name` attribute, which indicates which relation |
541 | to set data on, and a `provide_data()` method, which returns a dict of |
542 | data to set. |
543 | + |
544 | + The `provide_data()` method can optionally accept two parameters: |
545 | + |
546 | + * ``remote_service`` The name of the remote service that the data will |
547 | + be provided to. The `provide_data()` method will be called once |
548 | + for each connected service (not unit). This allows the method to |
549 | + tailor its data to the given service. |
550 | + * ``service_ready`` Whether or not the service definition had all of |
551 | + its requirements met, and thus the ``data_ready`` callbacks run. |
552 | + |
553 | + Note that the ``provided_data`` methods are now called **after** the |
554 | + ``data_ready`` callbacks are run. This gives the ``data_ready`` callbacks |
555 | + a chance to generate any data necessary for the providing to the remote |
556 | + services. |
557 | """ |
558 | - hook_name = hookenv.hook_name() |
559 | - for service in self.services.values(): |
560 | + for service_name, service in self.services.items(): |
561 | + service_ready = self.is_ready(service_name) |
562 | for provider in service.get('provided_data', []): |
563 | - if re.match(r'{}-relation-(joined|changed)'.format(provider.name), hook_name): |
564 | - data = provider.provide_data() |
565 | - _ready = provider._is_ready(data) if hasattr(provider, '_is_ready') else data |
566 | - if _ready: |
567 | - hookenv.relation_set(None, data) |
568 | + for relid in hookenv.relation_ids(provider.name): |
569 | + units = hookenv.related_units(relid) |
570 | + if not units: |
571 | + continue |
572 | + remote_service = units[0].split('/')[0] |
573 | + argspec = getargspec(provider.provide_data) |
574 | + if len(argspec.args) > 1: |
575 | + data = provider.provide_data(remote_service, service_ready) |
576 | + else: |
577 | + data = provider.provide_data() |
578 | + if data: |
579 | + hookenv.relation_set(relid, data) |
580 | |
581 | def reconfigure_services(self, *service_names): |
582 | """ |
583 | |
584 | === modified file 'hooks/charmhelpers/fetch/__init__.py' |
585 | --- hooks/charmhelpers/fetch/__init__.py 2015-05-01 14:56:06 +0000 |
586 | +++ hooks/charmhelpers/fetch/__init__.py 2015-06-04 23:12:46 +0000 |
587 | @@ -158,7 +158,7 @@ |
588 | |
589 | def apt_cache(in_memory=True): |
590 | """Build and return an apt cache""" |
591 | - import apt_pkg |
592 | + from apt import apt_pkg |
593 | apt_pkg.init() |
594 | if in_memory: |
595 | apt_pkg.config.set("Dir::Cache::pkgcache", "") |
596 | |
597 | === modified file 'unit_tests/test_glance_relations.py' |
598 | --- unit_tests/test_glance_relations.py 2015-05-12 19:46:43 +0000 |
599 | +++ unit_tests/test_glance_relations.py 2015-06-04 23:12:46 +0000 |
600 | @@ -24,7 +24,6 @@ |
601 | TO_PATCH = [ |
602 | # charmhelpers.core.hookenv |
603 | 'Hooks', |
604 | - 'canonical_url', |
605 | 'config', |
606 | 'juju_log', |
607 | 'is_relation_made', |
608 | @@ -320,8 +319,9 @@ |
609 | ) |
610 | self.migrate_database.assert_called_with() |
611 | |
612 | - def test_image_service_joined_leader(self): |
613 | - self.canonical_url.return_value = 'http://glancehost' |
614 | + @patch.object(relations, 'canonical_url') |
615 | + def test_image_service_joined_leader(self, _canonical_url): |
616 | + _canonical_url.return_value = 'http://glancehost' |
617 | relations.image_service_joined() |
618 | args = { |
619 | 'glance-api-server': 'http://glancehost:9292', |
620 | @@ -329,8 +329,9 @@ |
621 | } |
622 | self.relation_set.assert_called_with(**args) |
623 | |
624 | - def test_image_service_joined_specified_interface(self): |
625 | - self.canonical_url.return_value = 'http://glancehost' |
626 | + @patch.object(relations, 'canonical_url') |
627 | + def test_image_service_joined_specified_interface(self, _canonical_url): |
628 | + _canonical_url.return_value = 'http://glancehost' |
629 | relations.image_service_joined(relation_id='image-service:1') |
630 | args = { |
631 | 'glance-api-server': 'http://glancehost:9292', |
632 | @@ -417,10 +418,12 @@ |
633 | for c in [call('/etc/glance/glance.conf')]: |
634 | self.assertNotIn(c, configs.write.call_args_list) |
635 | |
636 | + @patch("charmhelpers.core.host.service") |
637 | @patch("glance_relations.relation_get", autospec=True) |
638 | @patch.object(relations, 'CONFIGS') |
639 | def test_ceph_changed_with_key_and_relation_data(self, configs, |
640 | - mock_relation_get): |
641 | + mock_relation_get, |
642 | + mock_service): |
643 | configs.complete_contexts = MagicMock() |
644 | configs.complete_contexts.return_value = ['ceph'] |
645 | configs.write = MagicMock() |
646 | @@ -439,8 +442,9 @@ |
647 | self.delete_keyring.assert_called_with(service='glance') |
648 | self.assertTrue(configs.write_all.called) |
649 | |
650 | - def test_keystone_joined(self): |
651 | - self.canonical_url.return_value = 'http://glancehost' |
652 | + @patch.object(relations, 'canonical_url') |
653 | + def test_keystone_joined(self, _canonical_url): |
654 | + _canonical_url.return_value = 'http://glancehost' |
655 | relations.keystone_joined() |
656 | ex = { |
657 | 'region': 'RegionOne', |
658 | @@ -452,8 +456,9 @@ |
659 | } |
660 | self.relation_set.assert_called_with(**ex) |
661 | |
662 | - def test_keystone_joined_with_relation_id(self): |
663 | - self.canonical_url.return_value = 'http://glancehost' |
664 | + @patch.object(relations, 'canonical_url') |
665 | + def test_keystone_joined_with_relation_id(self, _canonical_url): |
666 | + _canonical_url.return_value = 'http://glancehost' |
667 | relations.keystone_joined(relation_id='identity-service:0') |
668 | ex = { |
669 | 'region': 'RegionOne', |
670 | @@ -465,6 +470,26 @@ |
671 | } |
672 | self.relation_set.assert_called_with(**ex) |
673 | |
674 | + @patch('charmhelpers.contrib.openstack.ip.is_clustered') |
675 | + @patch('charmhelpers.contrib.openstack.ip.unit_get') |
676 | + @patch('charmhelpers.contrib.openstack.ip.config') |
677 | + def test_keystone_joined_public_endpoint(self, _config, _unit_get, |
678 | + _is_clustered): |
679 | + _unit_get.return_value = 'glancehost' |
680 | + _is_clustered.return_value = False |
681 | + self.test_config.set('os-public-hostname', 'glance.example.com') |
682 | + _config.side_effect = self.test_config.get |
683 | + relations.keystone_joined() |
684 | + ex = { |
685 | + 'region': 'RegionOne', |
686 | + 'public_url': 'http://glance.example.com:9292', |
687 | + 'admin_url': 'http://glancehost:9292', |
688 | + 'service': 'glance', |
689 | + 'internal_url': 'http://glancehost:9292', |
690 | + 'relation_id': None, |
691 | + } |
692 | + self.relation_set.assert_called_with(**ex) |
693 | + |
694 | @patch.object(relations, 'CONFIGS') |
695 | def test_keystone_changes_incomplete(self, configs): |
696 | configs.complete_contexts.return_value = [] |
697 | @@ -570,9 +595,11 @@ |
698 | call('/etc/haproxy/haproxy.cfg')], |
699 | configs.write.call_args_list) |
700 | |
701 | + @patch.object(relations, 'canonical_url') |
702 | @patch.object(relations, 'relation_set') |
703 | @patch.object(relations, 'CONFIGS') |
704 | - def test_cluster_changed_with_ipv6(self, configs, relation_set): |
705 | + def test_cluster_changed_with_ipv6(self, configs, relation_set, |
706 | + _canonical_url): |
707 | self.test_config.set('prefer-ipv6', True) |
708 | configs.complete_contexts = MagicMock() |
709 | configs.complete_contexts.return_value = ['cluster'] |
710 | @@ -683,10 +710,11 @@ |
711 | 'ha_changed: hacluster subordinate is not fully clustered.' |
712 | ) |
713 | |
714 | + @patch.object(relations, 'canonical_url') |
715 | @patch.object(relations, 'keystone_joined') |
716 | @patch.object(relations, 'CONFIGS') |
717 | def test_configure_https_enable_with_identity_service( |
718 | - self, configs, keystone_joined): |
719 | + self, configs, keystone_joined, _canonical_url): |
720 | configs.complete_contexts = MagicMock() |
721 | configs.complete_contexts.return_value = ['https'] |
722 | configs.write = MagicMock() |
723 | @@ -697,10 +725,11 @@ |
724 | self.check_call.assert_called_has_calls(calls) |
725 | keystone_joined.assert_called_with(relation_id='identity-service:0') |
726 | |
727 | + @patch.object(relations, 'canonical_url') |
728 | @patch.object(relations, 'keystone_joined') |
729 | @patch.object(relations, 'CONFIGS') |
730 | def test_configure_https_disable_with_keystone_joined( |
731 | - self, configs, keystone_joined): |
732 | + self, configs, keystone_joined, _canonical_url): |
733 | configs.complete_contexts = MagicMock() |
734 | configs.complete_contexts.return_value = [''] |
735 | configs.write = MagicMock() |
736 | @@ -711,10 +740,11 @@ |
737 | self.check_call.assert_called_has_calls(calls) |
738 | keystone_joined.assert_called_with(relation_id='identity-service:0') |
739 | |
740 | + @patch.object(relations, 'canonical_url') |
741 | @patch.object(relations, 'image_service_joined') |
742 | @patch.object(relations, 'CONFIGS') |
743 | def test_configure_https_enable_with_image_service( |
744 | - self, configs, image_service_joined): |
745 | + self, configs, image_service_joined, _canonical_url): |
746 | configs.complete_contexts = MagicMock() |
747 | configs.complete_contexts.return_value = ['https'] |
748 | configs.write = MagicMock() |
749 | @@ -725,10 +755,11 @@ |
750 | self.check_call.assert_called_has_calls(calls) |
751 | image_service_joined.assert_called_with(relation_id='image-service:0') |
752 | |
753 | + @patch.object(relations, 'canonical_url') |
754 | @patch.object(relations, 'image_service_joined') |
755 | @patch.object(relations, 'CONFIGS') |
756 | def test_configure_https_disable_with_image_service( |
757 | - self, configs, image_service_joined): |
758 | + self, configs, image_service_joined, _canonical_url): |
759 | configs.complete_contexts = MagicMock() |
760 | configs.complete_contexts.return_value = [''] |
761 | configs.write = MagicMock() |
charm_lint_check #5047 glance-next for billy-olsen mp261009
LINT OK: passed
Build: http:// 10.245. 162.77: 8080/job/ charm_lint_ check/5047/