Merge lp:~tribaal/charms/trusty/ceph/sync-charm-helpers into lp:~openstack-charmers-archive/charms/trusty/ceph/trunk
- Trusty Tahr (14.04)
- sync-charm-helpers
- Merge into 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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
OpenStack Charmers | Pending | ||
Review via email: mp+220037@code.launchpad.net |
Commit message
Description of the change
This branch syncs back the changes from charm-helpers as of http://
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) |