Merge lp:~tribaal/charms/trusty/ceph-radosgw/sync-charm-helpers into lp:~openstack-charmers-archive/charms/trusty/ceph-radosgw/trunk

Proposed by Chris Glass
Status: Merged
Merged at revision: 19
Proposed branch: lp:~tribaal/charms/trusty/ceph-radosgw/sync-charm-helpers
Merge into: lp:~openstack-charmers-archive/charms/trusty/ceph-radosgw/trunk
Diff against target: 445 lines (+236/-70)
4 files modified
hooks/charmhelpers/contrib/storage/linux/utils.py (+28/-5)
hooks/charmhelpers/core/hookenv.py (+98/-1)
hooks/charmhelpers/core/host.py (+14/-0)
hooks/charmhelpers/fetch/__init__.py (+96/-64)
To merge this branch: bzr merge lp:~tribaal/charms/trusty/ceph-radosgw/sync-charm-helpers
Reviewer Review Type Date Requested Status
OpenStack Charmers Pending
Review via email: mp+220024@code.launchpad.net

Description of the change

This branch syncs back the changes from charm-helpers as of http://bazaar.launchpad.net/~charm-helpers/charm-helpers/devel/revision/153?start_revid=153 into the charm.

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'hooks/charmhelpers/contrib/storage/linux/utils.py'
2--- hooks/charmhelpers/contrib/storage/linux/utils.py 2014-03-25 12:26:40 +0000
3+++ hooks/charmhelpers/contrib/storage/linux/utils.py 2014-05-19 12:47:46 +0000
4@@ -1,8 +1,11 @@
5-from os import stat
6+import os
7+import re
8 from stat import S_ISBLK
9
10 from subprocess import (
11- check_call
12+ check_call,
13+ check_output,
14+ call
15 )
16
17
18@@ -12,7 +15,9 @@
19
20 :returns: boolean: True if path is a block device, False if not.
21 '''
22- return S_ISBLK(stat(path).st_mode)
23+ if not os.path.exists(path):
24+ return False
25+ return S_ISBLK(os.stat(path).st_mode)
26
27
28 def zap_disk(block_device):
29@@ -22,5 +27,23 @@
30
31 :param block_device: str: Full path of block device to clean.
32 '''
33- check_call(['sgdisk', '--zap-all', '--clear',
34- '--mbrtogpt', block_device])
35+ # sometimes sgdisk exits non-zero; this is OK, dd will clean up
36+ call(['sgdisk', '--zap-all', '--mbrtogpt',
37+ '--clear', block_device])
38+ dev_end = check_output(['blockdev', '--getsz', block_device])
39+ gpt_end = int(dev_end.split()[0]) - 100
40+ check_call(['dd', 'if=/dev/zero', 'of=%s' % (block_device),
41+ 'bs=1M', 'count=1'])
42+ check_call(['dd', 'if=/dev/zero', 'of=%s' % (block_device),
43+ 'bs=512', 'count=100', 'seek=%s' % (gpt_end)])
44+
45+def is_device_mounted(device):
46+ '''Given a device path, return True if that device is mounted, and False
47+ if it isn't.
48+
49+ :param device: str: Full path of the device to check.
50+ :returns: boolean: True if the path represents a mounted device, False if
51+ it doesn't.
52+ '''
53+ out = check_output(['mount'])
54+ return bool(re.search(device + r"[0-9]+\b", out))
55
56=== modified file 'hooks/charmhelpers/core/hookenv.py'
57--- hooks/charmhelpers/core/hookenv.py 2014-03-25 12:26:40 +0000
58+++ hooks/charmhelpers/core/hookenv.py 2014-05-19 12:47:46 +0000
59@@ -155,6 +155,100 @@
60 return os.path.basename(sys.argv[0])
61
62
63+class Config(dict):
64+ """A Juju charm config dictionary that can write itself to
65+ disk (as json) and track which values have changed since
66+ the previous hook invocation.
67+
68+ Do not instantiate this object directly - instead call
69+ ``hookenv.config()``
70+
71+ Example usage::
72+
73+ >>> # inside a hook
74+ >>> from charmhelpers.core import hookenv
75+ >>> config = hookenv.config()
76+ >>> config['foo']
77+ 'bar'
78+ >>> config['mykey'] = 'myval'
79+ >>> config.save()
80+
81+
82+ >>> # user runs `juju set mycharm foo=baz`
83+ >>> # now we're inside subsequent config-changed hook
84+ >>> config = hookenv.config()
85+ >>> config['foo']
86+ 'baz'
87+ >>> # test to see if this val has changed since last hook
88+ >>> config.changed('foo')
89+ True
90+ >>> # what was the previous value?
91+ >>> config.previous('foo')
92+ 'bar'
93+ >>> # keys/values that we add are preserved across hooks
94+ >>> config['mykey']
95+ 'myval'
96+ >>> # don't forget to save at the end of hook!
97+ >>> config.save()
98+
99+ """
100+ CONFIG_FILE_NAME = '.juju-persistent-config'
101+
102+ def __init__(self, *args, **kw):
103+ super(Config, self).__init__(*args, **kw)
104+ self._prev_dict = None
105+ self.path = os.path.join(charm_dir(), Config.CONFIG_FILE_NAME)
106+ if os.path.exists(self.path):
107+ self.load_previous()
108+
109+ def load_previous(self, path=None):
110+ """Load previous copy of config from disk so that current values
111+ can be compared to previous values.
112+
113+ :param path:
114+
115+ File path from which to load the previous config. If `None`,
116+ config is loaded from the default location. If `path` is
117+ specified, subsequent `save()` calls will write to the same
118+ path.
119+
120+ """
121+ self.path = path or self.path
122+ with open(self.path) as f:
123+ self._prev_dict = json.load(f)
124+
125+ def changed(self, key):
126+ """Return true if the value for this key has changed since
127+ the last save.
128+
129+ """
130+ if self._prev_dict is None:
131+ return True
132+ return self.previous(key) != self.get(key)
133+
134+ def previous(self, key):
135+ """Return previous value for this key, or None if there
136+ is no "previous" value.
137+
138+ """
139+ if self._prev_dict:
140+ return self._prev_dict.get(key)
141+ return None
142+
143+ def save(self):
144+ """Save this config to disk.
145+
146+ Preserves items in _prev_dict that do not exist in self.
147+
148+ """
149+ if self._prev_dict:
150+ for k, v in self._prev_dict.iteritems():
151+ if k not in self:
152+ self[k] = v
153+ with open(self.path, 'w') as f:
154+ json.dump(self, f)
155+
156+
157 @cached
158 def config(scope=None):
159 """Juju charm configuration"""
160@@ -163,7 +257,10 @@
161 config_cmd_line.append(scope)
162 config_cmd_line.append('--format=json')
163 try:
164- return json.loads(subprocess.check_output(config_cmd_line))
165+ config_data = json.loads(subprocess.check_output(config_cmd_line))
166+ if scope is not None:
167+ return config_data
168+ return Config(config_data)
169 except ValueError:
170 return None
171
172
173=== modified file 'hooks/charmhelpers/core/host.py'
174--- hooks/charmhelpers/core/host.py 2014-03-25 12:26:40 +0000
175+++ hooks/charmhelpers/core/host.py 2014-05-19 12:47:46 +0000
176@@ -12,6 +12,7 @@
177 import string
178 import subprocess
179 import hashlib
180+import apt_pkg
181
182 from collections import OrderedDict
183
184@@ -295,3 +296,16 @@
185 if 'link/ether' in words:
186 hwaddr = words[words.index('link/ether') + 1]
187 return hwaddr
188+
189+
190+def cmp_pkgrevno(package, revno, pkgcache=None):
191+ '''Compare supplied revno with the revno of the installed package
192+ 1 => Installed revno is greater than supplied arg
193+ 0 => Installed revno is the same as supplied arg
194+ -1 => Installed revno is less than supplied arg
195+ '''
196+ if not pkgcache:
197+ apt_pkg.init()
198+ pkgcache = apt_pkg.Cache()
199+ pkg = pkgcache[package]
200+ return apt_pkg.version_compare(pkg.current_ver.ver_str, revno)
201
202=== modified file 'hooks/charmhelpers/fetch/__init__.py'
203--- hooks/charmhelpers/fetch/__init__.py 2014-04-24 17:27:30 +0000
204+++ hooks/charmhelpers/fetch/__init__.py 2014-05-19 12:47:46 +0000
205@@ -1,4 +1,5 @@
206 import importlib
207+import time
208 from yaml import safe_load
209 from charmhelpers.core.host import (
210 lsb_release
211@@ -15,6 +16,7 @@
212 import apt_pkg
213 import os
214
215+
216 CLOUD_ARCHIVE = """# Ubuntu Cloud Archive
217 deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main
218 """
219@@ -56,10 +58,62 @@
220 'precise-proposed/icehouse': 'precise-proposed/icehouse',
221 }
222
223+# The order of this list is very important. Handlers should be listed in from
224+# least- to most-specific URL matching.
225+FETCH_HANDLERS = (
226+ 'charmhelpers.fetch.archiveurl.ArchiveUrlFetchHandler',
227+ 'charmhelpers.fetch.bzrurl.BzrUrlFetchHandler',
228+)
229+
230+APT_NO_LOCK = 100 # The return code for "couldn't acquire lock" in APT.
231+APT_NO_LOCK_RETRY_DELAY = 10 # Wait 10 seconds between apt lock checks.
232+APT_NO_LOCK_RETRY_COUNT = 30 # Retry to acquire the lock X times.
233+
234+
235+class SourceConfigError(Exception):
236+ pass
237+
238+
239+class UnhandledSource(Exception):
240+ pass
241+
242+
243+class AptLockError(Exception):
244+ pass
245+
246+
247+class BaseFetchHandler(object):
248+
249+ """Base class for FetchHandler implementations in fetch plugins"""
250+
251+ def can_handle(self, source):
252+ """Returns True if the source can be handled. Otherwise returns
253+ a string explaining why it cannot"""
254+ return "Wrong source type"
255+
256+ def install(self, source):
257+ """Try to download and unpack the source. Return the path to the
258+ unpacked files or raise UnhandledSource."""
259+ raise UnhandledSource("Wrong source type {}".format(source))
260+
261+ def parse_url(self, url):
262+ return urlparse(url)
263+
264+ def base_url(self, url):
265+ """Return url without querystring or fragment"""
266+ parts = list(self.parse_url(url))
267+ parts[4:] = ['' for i in parts[4:]]
268+ return urlunparse(parts)
269+
270
271 def filter_installed_packages(packages):
272 """Returns a list of packages that require installation"""
273 apt_pkg.init()
274+
275+ # Tell apt to build an in-memory cache to prevent race conditions (if
276+ # another process is already building the cache).
277+ apt_pkg.config.set("Dir::Cache::pkgcache", "")
278+
279 cache = apt_pkg.Cache()
280 _pkgs = []
281 for package in packages:
282@@ -87,14 +141,7 @@
283 cmd.extend(packages)
284 log("Installing {} with options: {}".format(packages,
285 options))
286- env = os.environ.copy()
287- if 'DEBIAN_FRONTEND' not in env:
288- env['DEBIAN_FRONTEND'] = 'noninteractive'
289-
290- if fatal:
291- subprocess.check_call(cmd, env=env)
292- else:
293- subprocess.call(cmd, env=env)
294+ _run_apt_command(cmd, fatal)
295
296
297 def apt_upgrade(options=None, fatal=False, dist=False):
298@@ -109,24 +156,13 @@
299 else:
300 cmd.append('upgrade')
301 log("Upgrading with options: {}".format(options))
302-
303- env = os.environ.copy()
304- if 'DEBIAN_FRONTEND' not in env:
305- env['DEBIAN_FRONTEND'] = 'noninteractive'
306-
307- if fatal:
308- subprocess.check_call(cmd, env=env)
309- else:
310- subprocess.call(cmd, env=env)
311+ _run_apt_command(cmd, fatal)
312
313
314 def apt_update(fatal=False):
315 """Update local apt cache"""
316 cmd = ['apt-get', 'update']
317- if fatal:
318- subprocess.check_call(cmd)
319- else:
320- subprocess.call(cmd)
321+ _run_apt_command(cmd, fatal)
322
323
324 def apt_purge(packages, fatal=False):
325@@ -137,10 +173,7 @@
326 else:
327 cmd.extend(packages)
328 log("Purging {}".format(packages))
329- if fatal:
330- subprocess.check_call(cmd)
331- else:
332- subprocess.call(cmd)
333+ _run_apt_command(cmd, fatal)
334
335
336 def apt_hold(packages, fatal=False):
337@@ -151,6 +184,7 @@
338 else:
339 cmd.extend(packages)
340 log("Holding {}".format(packages))
341+
342 if fatal:
343 subprocess.check_call(cmd)
344 else:
345@@ -188,10 +222,6 @@
346 key])
347
348
349-class SourceConfigError(Exception):
350- pass
351-
352-
353 def configure_sources(update=False,
354 sources_var='install_sources',
355 keys_var='install_keys'):
356@@ -224,17 +254,6 @@
357 if update:
358 apt_update(fatal=True)
359
360-# The order of this list is very important. Handlers should be listed in from
361-# least- to most-specific URL matching.
362-FETCH_HANDLERS = (
363- 'charmhelpers.fetch.archiveurl.ArchiveUrlFetchHandler',
364- 'charmhelpers.fetch.bzrurl.BzrUrlFetchHandler',
365-)
366-
367-
368-class UnhandledSource(Exception):
369- pass
370-
371
372 def install_remote(source):
373 """
374@@ -265,30 +284,6 @@
375 return install_remote(source)
376
377
378-class BaseFetchHandler(object):
379-
380- """Base class for FetchHandler implementations in fetch plugins"""
381-
382- def can_handle(self, source):
383- """Returns True if the source can be handled. Otherwise returns
384- a string explaining why it cannot"""
385- return "Wrong source type"
386-
387- def install(self, source):
388- """Try to download and unpack the source. Return the path to the
389- unpacked files or raise UnhandledSource."""
390- raise UnhandledSource("Wrong source type {}".format(source))
391-
392- def parse_url(self, url):
393- return urlparse(url)
394-
395- def base_url(self, url):
396- """Return url without querystring or fragment"""
397- parts = list(self.parse_url(url))
398- parts[4:] = ['' for i in parts[4:]]
399- return urlunparse(parts)
400-
401-
402 def plugins(fetch_handlers=None):
403 if not fetch_handlers:
404 fetch_handlers = FETCH_HANDLERS
405@@ -306,3 +301,40 @@
406 log("FetchHandler {} not found, skipping plugin".format(
407 handler_name))
408 return plugin_list
409+
410+
411+def _run_apt_command(cmd, fatal=False):
412+ """
413+ Run an APT command, checking output and retrying if the fatal flag is set
414+ to True.
415+
416+ :param: cmd: str: The apt command to run.
417+ :param: fatal: bool: Whether the command's output should be checked and
418+ retried.
419+ """
420+ env = os.environ.copy()
421+
422+ if 'DEBIAN_FRONTEND' not in env:
423+ env['DEBIAN_FRONTEND'] = 'noninteractive'
424+
425+ if fatal:
426+ retry_count = 0
427+ result = None
428+
429+ # If the command is considered "fatal", we need to retry if the apt
430+ # lock was not acquired.
431+
432+ while result is None or result == APT_NO_LOCK:
433+ try:
434+ result = subprocess.check_call(cmd, env=env)
435+ except subprocess.CalledProcessError, e:
436+ retry_count = retry_count + 1
437+ if retry_count > APT_NO_LOCK_RETRY_COUNT:
438+ raise
439+ result = e.returncode
440+ log("Couldn't acquire DPKG lock. Will retry in {} seconds."
441+ "".format(APT_NO_LOCK_RETRY_DELAY))
442+ time.sleep(APT_NO_LOCK_RETRY_DELAY)
443+
444+ else:
445+ subprocess.call(cmd, env=env)

Subscribers

People subscribed via source and target branches