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

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

Subscribers

People subscribed via source and target branches