Merge lp:~billy-olsen/charms/trusty/glance/public-endpoint-host into lp:~openstack-charmers-archive/charms/trusty/glance/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
Reviewer Review Type Date Requested Status
Corey Bryant Approve
Review via email: mp+261009@code.launchpad.net

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 :

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/

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

Build: http://10.245.162.77:8080/job/charm_unit_test/4727/

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://10.245.162.77:8080/job/charm_amulet_test/4453/

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

Build: http://10.245.162.77:8080/job/charm_lint_check/5071/

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

Build: http://10.245.162.77:8080/job/charm_unit_test/4750/

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://10.245.162.77:8080/job/charm_amulet_test/4478/

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()

Subscribers

People subscribed via source and target branches