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

Proposed by Chris Glass
Status: Merged
Merged at revision: 73
Proposed branch: lp:~tribaal/charms/trusty/ceph/sync-charm-helpers
Merge into: lp:~openstack-charmers-archive/charms/trusty/ceph/trunk
Diff against target: 465 lines (+229/-75)
5 files modified
hooks/ceph.py (+2/-5)
hooks/charmhelpers/contrib/storage/linux/utils.py (+19/-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/sync-charm-helpers
Reviewer Review Type Date Requested Status
OpenStack Charmers Pending
Review via email: mp+220037@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.

Furthermore, the charm-helpers "is_device_mounted" function is used to prevent using mounted disks.

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

Subscribers

People subscribed via source and target branches