Merge lp:~le-charmers/charms/trusty/heat/leadership-election into lp:~openstack-charmers-archive/charms/trusty/heat/next

Proposed by Edward Hope-Morley
Status: Merged
Merged at revision: 42
Proposed branch: lp:~le-charmers/charms/trusty/heat/leadership-election
Merge into: lp:~openstack-charmers-archive/charms/trusty/heat/next
Diff against target: 729 lines (+328/-58)
9 files modified
hooks/charmhelpers/contrib/hahelpers/cluster.py (+37/-2)
hooks/charmhelpers/contrib/openstack/neutron.py (+10/-5)
hooks/charmhelpers/contrib/openstack/utils.py (+65/-18)
hooks/charmhelpers/contrib/python/packages.py (+28/-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)
hooks/charmhelpers/fetch/giturl.py (+7/-5)
To merge this branch: bzr merge lp:~le-charmers/charms/trusty/heat/leadership-election
Reviewer Review Type Date Requested Status
OpenStack Charmers Pending
Review via email: mp+255009@code.launchpad.net
To post a comment you must log in.
39. By Liam Young

Merged trunk in + LE charmhelper sync

40. By Liam Young

Resync le charm helpers

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'hooks/charmhelpers/contrib/hahelpers/cluster.py'
2--- hooks/charmhelpers/contrib/hahelpers/cluster.py 2015-02-24 11:04:31 +0000
3+++ hooks/charmhelpers/contrib/hahelpers/cluster.py 2015-06-04 08:45:30 +0000
4@@ -44,6 +44,7 @@
5 ERROR,
6 WARNING,
7 unit_get,
8+ is_leader as juju_is_leader
9 )
10 from charmhelpers.core.decorators import (
11 retry_on_exception,
12@@ -52,6 +53,8 @@
13 bool_from_string,
14 )
15
16+DC_RESOURCE_NAME = 'DC'
17+
18
19 class HAIncompleteConfig(Exception):
20 pass
21@@ -66,12 +69,21 @@
22 Returns True if the charm executing this is the elected cluster leader.
23
24 It relies on two mechanisms to determine leadership:
25- 1. If the charm is part of a corosync cluster, call corosync to
26+ 1. If juju is sufficiently new and leadership election is supported,
27+ the is_leader command will be used.
28+ 2. If the charm is part of a corosync cluster, call corosync to
29 determine leadership.
30- 2. If the charm is not part of a corosync cluster, the leader is
31+ 3. If the charm is not part of a corosync cluster, the leader is
32 determined as being "the alive unit with the lowest unit numer". In
33 other words, the oldest surviving unit.
34 """
35+ try:
36+ return juju_is_leader()
37+ except NotImplementedError:
38+ log('Juju leadership election feature not enabled'
39+ ', using fallback support',
40+ level=WARNING)
41+
42 if is_clustered():
43 if not is_crm_leader(resource):
44 log('Deferring action to CRM leader.', level=INFO)
45@@ -95,6 +107,27 @@
46 return False
47
48
49+def is_crm_dc():
50+ """
51+ Determine leadership by querying the pacemaker Designated Controller
52+ """
53+ cmd = ['crm', 'status']
54+ try:
55+ status = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
56+ if not isinstance(status, six.text_type):
57+ status = six.text_type(status, "utf-8")
58+ except subprocess.CalledProcessError:
59+ return False
60+ current_dc = ''
61+ for line in status.split('\n'):
62+ if line.startswith('Current DC'):
63+ # Current DC: juju-lytrusty-machine-2 (168108163) - partition with quorum
64+ current_dc = line.split(':')[1].split()[0]
65+ if current_dc == get_unit_hostname():
66+ return True
67+ return False
68+
69+
70 @retry_on_exception(5, base_delay=2, exc_type=CRMResourceNotFound)
71 def is_crm_leader(resource, retry=False):
72 """
73@@ -104,6 +137,8 @@
74 We allow this operation to be retried to avoid the possibility of getting a
75 false negative. See LP #1396246 for more info.
76 """
77+ if resource == DC_RESOURCE_NAME:
78+ return is_crm_dc()
79 cmd = ['crm', 'resource', 'show', resource]
80 try:
81 status = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
82
83=== modified file 'hooks/charmhelpers/contrib/openstack/neutron.py'
84--- hooks/charmhelpers/contrib/openstack/neutron.py 2015-04-16 10:29:48 +0000
85+++ hooks/charmhelpers/contrib/openstack/neutron.py 2015-06-04 08:45:30 +0000
86@@ -256,11 +256,14 @@
87 def parse_mappings(mappings):
88 parsed = {}
89 if mappings:
90- mappings = mappings.split(' ')
91+ mappings = mappings.split()
92 for m in mappings:
93 p = m.partition(':')
94- if p[1] == ':':
95- parsed[p[0].strip()] = p[2].strip()
96+ key = p[0].strip()
97+ if p[1]:
98+ parsed[key] = p[2].strip()
99+ else:
100+ parsed[key] = ''
101
102 return parsed
103
104@@ -283,13 +286,13 @@
105 Returns dict of the form {bridge:port}.
106 """
107 _mappings = parse_mappings(mappings)
108- if not _mappings:
109+ if not _mappings or list(_mappings.values()) == ['']:
110 if not mappings:
111 return {}
112
113 # For backwards-compatibility we need to support port-only provided in
114 # config.
115- _mappings = {default_bridge: mappings.split(' ')[0]}
116+ _mappings = {default_bridge: mappings.split()[0]}
117
118 bridges = _mappings.keys()
119 ports = _mappings.values()
120@@ -309,6 +312,8 @@
121
122 Mappings must be a space-delimited list of provider:start:end mappings.
123
124+ The start:end range is optional and may be omitted.
125+
126 Returns dict of the form {provider: (start, end)}.
127 """
128 _mappings = parse_mappings(mappings)
129
130=== modified file 'hooks/charmhelpers/contrib/openstack/utils.py'
131--- hooks/charmhelpers/contrib/openstack/utils.py 2015-04-16 21:33:43 +0000
132+++ hooks/charmhelpers/contrib/openstack/utils.py 2015-06-04 08:45:30 +0000
133@@ -53,9 +53,13 @@
134 get_ipv6_addr
135 )
136
137+from charmhelpers.contrib.python.packages import (
138+ pip_create_virtualenv,
139+ pip_install,
140+)
141+
142 from charmhelpers.core.host import lsb_release, mounts, umount
143 from charmhelpers.fetch import apt_install, apt_cache, install_remote
144-from charmhelpers.contrib.python.packages import pip_install
145 from charmhelpers.contrib.storage.linux.utils import is_block_device, zap_disk
146 from charmhelpers.contrib.storage.linux.loopback import ensure_loopback_device
147
148@@ -497,7 +501,17 @@
149 requirements_dir = None
150
151
152-def git_clone_and_install(projects_yaml, core_project):
153+def _git_yaml_load(projects_yaml):
154+ """
155+ Load the specified yaml into a dictionary.
156+ """
157+ if not projects_yaml:
158+ return None
159+
160+ return yaml.load(projects_yaml)
161+
162+
163+def git_clone_and_install(projects_yaml, core_project, depth=1):
164 """
165 Clone/install all specified OpenStack repositories.
166
167@@ -510,23 +524,22 @@
168 repository: 'git://git.openstack.org/openstack/requirements.git',
169 branch: 'stable/icehouse'}
170 directory: /mnt/openstack-git
171- http_proxy: http://squid.internal:3128
172- https_proxy: https://squid.internal:3128
173+ http_proxy: squid-proxy-url
174+ https_proxy: squid-proxy-url
175
176 The directory, http_proxy, and https_proxy keys are optional.
177 """
178 global requirements_dir
179 parent_dir = '/mnt/openstack-git'
180-
181- if not projects_yaml:
182- return
183-
184- projects = yaml.load(projects_yaml)
185+ http_proxy = None
186+
187+ projects = _git_yaml_load(projects_yaml)
188 _git_validate_projects_yaml(projects, core_project)
189
190 old_environ = dict(os.environ)
191
192 if 'http_proxy' in projects.keys():
193+ http_proxy = projects['http_proxy']
194 os.environ['http_proxy'] = projects['http_proxy']
195 if 'https_proxy' in projects.keys():
196 os.environ['https_proxy'] = projects['https_proxy']
197@@ -534,15 +547,19 @@
198 if 'directory' in projects.keys():
199 parent_dir = projects['directory']
200
201+ pip_create_virtualenv(os.path.join(parent_dir, 'venv'))
202+
203 for p in projects['repositories']:
204 repo = p['repository']
205 branch = p['branch']
206 if p['name'] == 'requirements':
207- repo_dir = _git_clone_and_install_single(repo, branch, parent_dir,
208+ repo_dir = _git_clone_and_install_single(repo, branch, depth,
209+ parent_dir, http_proxy,
210 update_requirements=False)
211 requirements_dir = repo_dir
212 else:
213- repo_dir = _git_clone_and_install_single(repo, branch, parent_dir,
214+ repo_dir = _git_clone_and_install_single(repo, branch, depth,
215+ parent_dir, http_proxy,
216 update_requirements=True)
217
218 os.environ = old_environ
219@@ -574,7 +591,8 @@
220 error_out('openstack-origin-git key \'{}\' is missing'.format(key))
221
222
223-def _git_clone_and_install_single(repo, branch, parent_dir, update_requirements):
224+def _git_clone_and_install_single(repo, branch, depth, parent_dir, http_proxy,
225+ update_requirements):
226 """
227 Clone and install a single git repository.
228 """
229@@ -587,7 +605,8 @@
230
231 if not os.path.exists(dest_dir):
232 juju_log('Cloning git repo: {}, branch: {}'.format(repo, branch))
233- repo_dir = install_remote(repo, dest=parent_dir, branch=branch)
234+ repo_dir = install_remote(repo, dest=parent_dir, branch=branch,
235+ depth=depth)
236 else:
237 repo_dir = dest_dir
238
239@@ -598,7 +617,12 @@
240 _git_update_requirements(repo_dir, requirements_dir)
241
242 juju_log('Installing git repo from dir: {}'.format(repo_dir))
243- pip_install(repo_dir)
244+ if http_proxy:
245+ pip_install(repo_dir, proxy=http_proxy,
246+ venv=os.path.join(parent_dir, 'venv'))
247+ else:
248+ pip_install(repo_dir,
249+ venv=os.path.join(parent_dir, 'venv'))
250
251 return repo_dir
252
253@@ -621,16 +645,27 @@
254 os.chdir(orig_dir)
255
256
257+def git_pip_venv_dir(projects_yaml):
258+ """
259+ Return the pip virtualenv path.
260+ """
261+ parent_dir = '/mnt/openstack-git'
262+
263+ projects = _git_yaml_load(projects_yaml)
264+
265+ if 'directory' in projects.keys():
266+ parent_dir = projects['directory']
267+
268+ return os.path.join(parent_dir, 'venv')
269+
270+
271 def git_src_dir(projects_yaml, project):
272 """
273 Return the directory where the specified project's source is located.
274 """
275 parent_dir = '/mnt/openstack-git'
276
277- if not projects_yaml:
278- return
279-
280- projects = yaml.load(projects_yaml)
281+ projects = _git_yaml_load(projects_yaml)
282
283 if 'directory' in projects.keys():
284 parent_dir = projects['directory']
285@@ -640,3 +675,15 @@
286 return os.path.join(parent_dir, os.path.basename(p['repository']))
287
288 return None
289+
290+
291+def git_yaml_value(projects_yaml, key):
292+ """
293+ Return the value in projects_yaml for the specified key.
294+ """
295+ projects = _git_yaml_load(projects_yaml)
296+
297+ if key in projects.keys():
298+ return projects[key]
299+
300+ return None
301
302=== modified file 'hooks/charmhelpers/contrib/python/packages.py'
303--- hooks/charmhelpers/contrib/python/packages.py 2015-02-24 11:04:31 +0000
304+++ hooks/charmhelpers/contrib/python/packages.py 2015-06-04 08:45:30 +0000
305@@ -17,8 +17,11 @@
306 # You should have received a copy of the GNU Lesser General Public License
307 # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
308
309+import os
310+import subprocess
311+
312 from charmhelpers.fetch import apt_install, apt_update
313-from charmhelpers.core.hookenv import log
314+from charmhelpers.core.hookenv import charm_dir, log
315
316 try:
317 from pip import main as pip_execute
318@@ -51,11 +54,15 @@
319 pip_execute(command)
320
321
322-def pip_install(package, fatal=False, upgrade=False, **options):
323+def pip_install(package, fatal=False, upgrade=False, venv=None, **options):
324 """Install a python package"""
325- command = ["install"]
326+ if venv:
327+ venv_python = os.path.join(venv, 'bin/pip')
328+ command = [venv_python, "install"]
329+ else:
330+ command = ["install"]
331
332- available_options = ('proxy', 'src', 'log', "index-url", )
333+ available_options = ('proxy', 'src', 'log', 'index-url', )
334 for option in parse_options(options, available_options):
335 command.append(option)
336
337@@ -69,7 +76,10 @@
338
339 log("Installing {} package with options: {}".format(package,
340 command))
341- pip_execute(command)
342+ if venv:
343+ subprocess.check_call(command)
344+ else:
345+ pip_execute(command)
346
347
348 def pip_uninstall(package, **options):
349@@ -94,3 +104,16 @@
350 """Returns the list of current python installed packages
351 """
352 return pip_execute(["list"])
353+
354+
355+def pip_create_virtualenv(path=None):
356+ """Create an isolated Python environment."""
357+ apt_install('python-virtualenv')
358+
359+ if path:
360+ venv_path = path
361+ else:
362+ venv_path = os.path.join(charm_dir(), 'venv')
363+
364+ if not os.path.exists(venv_path):
365+ subprocess.check_call(['virtualenv', venv_path])
366
367=== modified file 'hooks/charmhelpers/core/hookenv.py'
368--- hooks/charmhelpers/core/hookenv.py 2015-04-16 10:29:48 +0000
369+++ hooks/charmhelpers/core/hookenv.py 2015-06-04 08:45:30 +0000
370@@ -21,12 +21,14 @@
371 # Charm Helpers Developers <juju@lists.ubuntu.com>
372
373 from __future__ import print_function
374+from functools import wraps
375 import os
376 import json
377 import yaml
378 import subprocess
379 import sys
380 import errno
381+import tempfile
382 from subprocess import CalledProcessError
383
384 import six
385@@ -58,15 +60,17 @@
386
387 will cache the result of unit_get + 'test' for future calls.
388 """
389+ @wraps(func)
390 def wrapper(*args, **kwargs):
391 global cache
392 key = str((func, args, kwargs))
393 try:
394 return cache[key]
395 except KeyError:
396- res = func(*args, **kwargs)
397- cache[key] = res
398- return res
399+ pass # Drop out of the exception handler scope.
400+ res = func(*args, **kwargs)
401+ cache[key] = res
402+ return res
403 return wrapper
404
405
406@@ -178,7 +182,7 @@
407
408 def remote_unit():
409 """The remote unit for the current relation hook"""
410- return os.environ['JUJU_REMOTE_UNIT']
411+ return os.environ.get('JUJU_REMOTE_UNIT', None)
412
413
414 def service_name():
415@@ -250,6 +254,12 @@
416 except KeyError:
417 return (self._prev_dict or {})[key]
418
419+ def get(self, key, default=None):
420+ try:
421+ return self[key]
422+ except KeyError:
423+ return default
424+
425 def keys(self):
426 prev_keys = []
427 if self._prev_dict is not None:
428@@ -353,18 +363,49 @@
429 """Set relation information for the current unit"""
430 relation_settings = relation_settings if relation_settings else {}
431 relation_cmd_line = ['relation-set']
432+ accepts_file = "--file" in subprocess.check_output(
433+ relation_cmd_line + ["--help"], universal_newlines=True)
434 if relation_id is not None:
435 relation_cmd_line.extend(('-r', relation_id))
436- for k, v in (list(relation_settings.items()) + list(kwargs.items())):
437- if v is None:
438- relation_cmd_line.append('{}='.format(k))
439- else:
440- relation_cmd_line.append('{}={}'.format(k, v))
441- subprocess.check_call(relation_cmd_line)
442+ settings = relation_settings.copy()
443+ settings.update(kwargs)
444+ for key, value in settings.items():
445+ # Force value to be a string: it always should, but some call
446+ # sites pass in things like dicts or numbers.
447+ if value is not None:
448+ settings[key] = "{}".format(value)
449+ if accepts_file:
450+ # --file was introduced in Juju 1.23.2. Use it by default if
451+ # available, since otherwise we'll break if the relation data is
452+ # too big. Ideally we should tell relation-set to read the data from
453+ # stdin, but that feature is broken in 1.23.2: Bug #1454678.
454+ with tempfile.NamedTemporaryFile(delete=False) as settings_file:
455+ settings_file.write(yaml.safe_dump(settings).encode("utf-8"))
456+ subprocess.check_call(
457+ relation_cmd_line + ["--file", settings_file.name])
458+ os.remove(settings_file.name)
459+ else:
460+ for key, value in settings.items():
461+ if value is None:
462+ relation_cmd_line.append('{}='.format(key))
463+ else:
464+ relation_cmd_line.append('{}={}'.format(key, value))
465+ subprocess.check_call(relation_cmd_line)
466 # Flush cache of any relation-gets for local unit
467 flush(local_unit())
468
469
470+def relation_clear(r_id=None):
471+ ''' Clears any relation data already set on relation r_id '''
472+ settings = relation_get(rid=r_id,
473+ unit=local_unit())
474+ for setting in settings:
475+ if setting not in ['public-address', 'private-address']:
476+ settings[setting] = None
477+ relation_set(relation_id=r_id,
478+ **settings)
479+
480+
481 @cached
482 def relation_ids(reltype=None):
483 """A list of relation_ids"""
484@@ -509,6 +550,11 @@
485 return None
486
487
488+def unit_public_ip():
489+ """Get this unit's public IP address"""
490+ return unit_get('public-address')
491+
492+
493 def unit_private_ip():
494 """Get this unit's private IP address"""
495 return unit_get('private-address')
496@@ -605,3 +651,94 @@
497
498 The results set by action_set are preserved."""
499 subprocess.check_call(['action-fail', message])
500+
501+
502+def status_set(workload_state, message):
503+ """Set the workload state with a message
504+
505+ Use status-set to set the workload state with a message which is visible
506+ to the user via juju status. If the status-set command is not found then
507+ assume this is juju < 1.23 and juju-log the message unstead.
508+
509+ workload_state -- valid juju workload state.
510+ message -- status update message
511+ """
512+ valid_states = ['maintenance', 'blocked', 'waiting', 'active']
513+ if workload_state not in valid_states:
514+ raise ValueError(
515+ '{!r} is not a valid workload state'.format(workload_state)
516+ )
517+ cmd = ['status-set', workload_state, message]
518+ try:
519+ ret = subprocess.call(cmd)
520+ if ret == 0:
521+ return
522+ except OSError as e:
523+ if e.errno != errno.ENOENT:
524+ raise
525+ log_message = 'status-set failed: {} {}'.format(workload_state,
526+ message)
527+ log(log_message, level='INFO')
528+
529+
530+def status_get():
531+ """Retrieve the previously set juju workload state
532+
533+ If the status-set command is not found then assume this is juju < 1.23 and
534+ return 'unknown'
535+ """
536+ cmd = ['status-get']
537+ try:
538+ raw_status = subprocess.check_output(cmd, universal_newlines=True)
539+ status = raw_status.rstrip()
540+ return status
541+ except OSError as e:
542+ if e.errno == errno.ENOENT:
543+ return 'unknown'
544+ else:
545+ raise
546+
547+
548+def translate_exc(from_exc, to_exc):
549+ def inner_translate_exc1(f):
550+ def inner_translate_exc2(*args, **kwargs):
551+ try:
552+ return f(*args, **kwargs)
553+ except from_exc:
554+ raise to_exc
555+
556+ return inner_translate_exc2
557+
558+ return inner_translate_exc1
559+
560+
561+@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
562+def is_leader():
563+ """Does the current unit hold the juju leadership
564+
565+ Uses juju to determine whether the current unit is the leader of its peers
566+ """
567+ cmd = ['is-leader', '--format=json']
568+ return json.loads(subprocess.check_output(cmd).decode('UTF-8'))
569+
570+
571+@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
572+def leader_get(attribute=None):
573+ """Juju leader get value(s)"""
574+ cmd = ['leader-get', '--format=json'] + [attribute or '-']
575+ return json.loads(subprocess.check_output(cmd).decode('UTF-8'))
576+
577+
578+@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
579+def leader_set(settings=None, **kwargs):
580+ """Juju leader set value(s)"""
581+ log("Juju leader-set '%s'" % (settings), level=DEBUG)
582+ cmd = ['leader-set']
583+ settings = settings or {}
584+ settings.update(kwargs)
585+ for k, v in settings.iteritems():
586+ if v is None:
587+ cmd.append('{}='.format(k))
588+ else:
589+ cmd.append('{}={}'.format(k, v))
590+ subprocess.check_call(cmd)
591
592=== modified file 'hooks/charmhelpers/core/host.py'
593--- hooks/charmhelpers/core/host.py 2015-04-01 12:18:48 +0000
594+++ hooks/charmhelpers/core/host.py 2015-06-04 08:45:30 +0000
595@@ -90,7 +90,7 @@
596 ['service', service_name, 'status'],
597 stderr=subprocess.STDOUT).decode('UTF-8')
598 except subprocess.CalledProcessError as e:
599- return 'unrecognized service' not in e.output
600+ return b'unrecognized service' not in e.output
601 else:
602 return True
603
604
605=== modified file 'hooks/charmhelpers/core/services/base.py'
606--- hooks/charmhelpers/core/services/base.py 2015-01-26 09:36:58 +0000
607+++ hooks/charmhelpers/core/services/base.py 2015-06-04 08:45:30 +0000
608@@ -15,9 +15,9 @@
609 # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
610
611 import os
612-import re
613 import json
614-from collections import Iterable
615+from inspect import getargspec
616+from collections import Iterable, OrderedDict
617
618 from charmhelpers.core import host
619 from charmhelpers.core import hookenv
620@@ -119,7 +119,7 @@
621 """
622 self._ready_file = os.path.join(hookenv.charm_dir(), 'READY-SERVICES.json')
623 self._ready = None
624- self.services = {}
625+ self.services = OrderedDict()
626 for service in services or []:
627 service_name = service['service']
628 self.services[service_name] = service
629@@ -132,8 +132,8 @@
630 if hook_name == 'stop':
631 self.stop_services()
632 else:
633+ self.reconfigure_services()
634 self.provide_data()
635- self.reconfigure_services()
636 cfg = hookenv.config()
637 if cfg.implicit_save:
638 cfg.save()
639@@ -145,15 +145,36 @@
640 A provider must have a `name` attribute, which indicates which relation
641 to set data on, and a `provide_data()` method, which returns a dict of
642 data to set.
643+
644+ The `provide_data()` method can optionally accept two parameters:
645+
646+ * ``remote_service`` The name of the remote service that the data will
647+ be provided to. The `provide_data()` method will be called once
648+ for each connected service (not unit). This allows the method to
649+ tailor its data to the given service.
650+ * ``service_ready`` Whether or not the service definition had all of
651+ its requirements met, and thus the ``data_ready`` callbacks run.
652+
653+ Note that the ``provided_data`` methods are now called **after** the
654+ ``data_ready`` callbacks are run. This gives the ``data_ready`` callbacks
655+ a chance to generate any data necessary for the providing to the remote
656+ services.
657 """
658- hook_name = hookenv.hook_name()
659- for service in self.services.values():
660+ for service_name, service in self.services.items():
661+ service_ready = self.is_ready(service_name)
662 for provider in service.get('provided_data', []):
663- if re.match(r'{}-relation-(joined|changed)'.format(provider.name), hook_name):
664- data = provider.provide_data()
665- _ready = provider._is_ready(data) if hasattr(provider, '_is_ready') else data
666- if _ready:
667- hookenv.relation_set(None, data)
668+ for relid in hookenv.relation_ids(provider.name):
669+ units = hookenv.related_units(relid)
670+ if not units:
671+ continue
672+ remote_service = units[0].split('/')[0]
673+ argspec = getargspec(provider.provide_data)
674+ if len(argspec.args) > 1:
675+ data = provider.provide_data(remote_service, service_ready)
676+ else:
677+ data = provider.provide_data()
678+ if data:
679+ hookenv.relation_set(relid, data)
680
681 def reconfigure_services(self, *service_names):
682 """
683
684=== modified file 'hooks/charmhelpers/fetch/__init__.py'
685--- hooks/charmhelpers/fetch/__init__.py 2015-01-26 09:36:58 +0000
686+++ hooks/charmhelpers/fetch/__init__.py 2015-06-04 08:45:30 +0000
687@@ -158,7 +158,7 @@
688
689 def apt_cache(in_memory=True):
690 """Build and return an apt cache"""
691- import apt_pkg
692+ from apt import apt_pkg
693 apt_pkg.init()
694 if in_memory:
695 apt_pkg.config.set("Dir::Cache::pkgcache", "")
696
697=== modified file 'hooks/charmhelpers/fetch/giturl.py'
698--- hooks/charmhelpers/fetch/giturl.py 2015-02-24 11:04:31 +0000
699+++ hooks/charmhelpers/fetch/giturl.py 2015-06-04 08:45:30 +0000
700@@ -45,14 +45,16 @@
701 else:
702 return True
703
704- def clone(self, source, dest, branch):
705+ def clone(self, source, dest, branch, depth=None):
706 if not self.can_handle(source):
707 raise UnhandledSource("Cannot handle {}".format(source))
708
709- repo = Repo.clone_from(source, dest)
710- repo.git.checkout(branch)
711+ if depth:
712+ Repo.clone_from(source, dest, branch=branch, depth=depth)
713+ else:
714+ Repo.clone_from(source, dest, branch=branch)
715
716- def install(self, source, branch="master", dest=None):
717+ def install(self, source, branch="master", dest=None, depth=None):
718 url_parts = self.parse_url(source)
719 branch_name = url_parts.path.strip("/").split("/")[-1]
720 if dest:
721@@ -63,7 +65,7 @@
722 if not os.path.exists(dest_dir):
723 mkdir(dest_dir, perms=0o755)
724 try:
725- self.clone(source, dest_dir, branch)
726+ self.clone(source, dest_dir, branch, depth)
727 except GitCommandError as e:
728 raise UnhandledSource(e.message)
729 except OSError as e:

Subscribers

People subscribed via source and target branches