Merge lp:~james-page/charms/trusty/mongodb/ch-resync-newton into lp:charms/trusty/mongodb

Proposed by James Page
Status: Merged
Merged at revision: 88
Proposed branch: lp:~james-page/charms/trusty/mongodb/ch-resync-newton
Merge into: lp:charms/trusty/mongodb
Diff against target: 536 lines (+223/-61)
5 files modified
charmhelpers/contrib/python/packages.py (+22/-7)
charmhelpers/core/hookenv.py (+31/-0)
charmhelpers/core/host.py (+159/-53)
charmhelpers/fetch/__init__.py (+8/-0)
charmhelpers/fetch/giturl.py (+3/-1)
To merge this branch: bzr merge lp:~james-page/charms/trusty/mongodb/ch-resync-newton
Reviewer Review Type Date Requested Status
Review Queue (community) automated testing Needs Fixing
Ryan Beisner (community) Approve
Review via email: mp+297046@code.launchpad.net

Description of the change

Resync charm-helpers to ensure that charm understands the Newton UCA.

To post a comment you must log in.
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_lint_check #2850 mongodb for james-page mp297046
    LINT OK: passed

Build: http://10.245.162.36:8080/job/charm_lint_check/2850/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_unit_test #2174 mongodb for james-page mp297046
    UNIT OK: passed

Build: http://10.245.162.36:8080/job/charm_unit_test/2174/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_amulet_test #779 mongodb for james-page mp297046
    AMULET FAIL: amulet-test failed

AMULET Results (max last 2 lines):
make: *** [functional_test] Error 1
ERROR:root:Make target returned non-zero.

Full amulet test output: http://paste.ubuntu.com/17168378/
Build: http://10.245.162.36:8080/job/charm_amulet_test/779/

Revision history for this message
Ryan Beisner (1chb1n) wrote :

I believe the amulet test failure is an existing problem with the 03_deploy_replicaset.py test logic, https://bugs.launchpad.net/charms/+source/mongodb/+bug/1518468.

00:30:46.446 juju-test.conductor DEBUG : State for 1.25.5: started
00:30:46.447 juju-test.conductor.03_deploy_replicaset.py DEBUG : Running 03_deploy_replicaset.py (tests/03_deploy_replicaset.py)
00:30:47.717 2016-06-10 11:34:08 Starting deployment of osci-sv08
00:30:48.010 2016-06-10 11:34:08 Deploying services...
00:30:48.081 2016-06-10 11:34:08 Deploying service mongodb using /tmp/charmn47kajm4/trusty/mongodb
00:34:24.871 2016-06-10 11:37:45 Adding relations...
00:34:24.950 2016-06-10 11:37:45 Exposing service 'mongodb'
00:34:25.167 2016-06-10 11:37:45 Deployment complete in 217.69 seconds
00:35:05.382 juju-test.conductor.03_deploy_replicaset.py DEBUG : Expected 2 secondary units! (Found 0) [5, 5, 1]
00:35:05.382
00:35:05.382 juju-test.conductor.03_deploy_replicaset.py DEBUG : Got exit code: 1
00:35:05.382 juju-test.conductor.03_deploy_replicaset.py RESULT : FAIL
00:35:05.382 juju-test.conductor INFO : Breaking here as requested by --set-e
00:35:05.382 juju-test INFO : Results: 3 passed, 1 failed, 0 errored
00:35:05.397 make: *** [functional_test] Error 1

review: Approve
Revision history for this message
Review Queue (review-queue) wrote :

This item has failed automated testing! Results available here http://juju-ci.vapour.ws:8080/job/charm-bundle-test-aws/4675/

review: Needs Fixing (automated testing)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charmhelpers/contrib/python/packages.py'
2--- charmhelpers/contrib/python/packages.py 2016-01-11 18:16:28 +0000
3+++ charmhelpers/contrib/python/packages.py 2016-06-10 10:55:43 +0000
4@@ -19,20 +19,35 @@
5
6 import os
7 import subprocess
8+import sys
9
10 from charmhelpers.fetch import apt_install, apt_update
11 from charmhelpers.core.hookenv import charm_dir, log
12
13-try:
14- from pip import main as pip_execute
15-except ImportError:
16- apt_update()
17- apt_install('python-pip')
18- from pip import main as pip_execute
19-
20 __author__ = "Jorge Niedbalski <jorge.niedbalski@canonical.com>"
21
22
23+def pip_execute(*args, **kwargs):
24+ """Overriden pip_execute() to stop sys.path being changed.
25+
26+ The act of importing main from the pip module seems to cause add wheels
27+ from the /usr/share/python-wheels which are installed by various tools.
28+ This function ensures that sys.path remains the same after the call is
29+ executed.
30+ """
31+ try:
32+ _path = sys.path
33+ try:
34+ from pip import main as _pip_execute
35+ except ImportError:
36+ apt_update()
37+ apt_install('python-pip')
38+ from pip import main as _pip_execute
39+ _pip_execute(*args, **kwargs)
40+ finally:
41+ sys.path = _path
42+
43+
44 def parse_options(given, available):
45 """Given a set of options, check if available"""
46 for key, value in sorted(given.items()):
47
48=== modified file 'charmhelpers/core/hookenv.py'
49--- charmhelpers/core/hookenv.py 2016-01-11 18:16:28 +0000
50+++ charmhelpers/core/hookenv.py 2016-06-10 10:55:43 +0000
51@@ -912,6 +912,24 @@
52 subprocess.check_call(cmd)
53
54
55+@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
56+def resource_get(name):
57+ """used to fetch the resource path of the given name.
58+
59+ <name> must match a name of defined resource in metadata.yaml
60+
61+ returns either a path or False if resource not available
62+ """
63+ if not name:
64+ return False
65+
66+ cmd = ['resource-get', name]
67+ try:
68+ return subprocess.check_output(cmd).decode('UTF-8')
69+ except subprocess.CalledProcessError:
70+ return False
71+
72+
73 @cached
74 def juju_version():
75 """Full version string (eg. '1.23.3.1-trusty-amd64')"""
76@@ -976,3 +994,16 @@
77 for callback, args, kwargs in reversed(_atexit):
78 callback(*args, **kwargs)
79 del _atexit[:]
80+
81+
82+@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
83+def network_get_primary_address(binding):
84+ '''
85+ Retrieve the primary network address for a named binding
86+
87+ :param binding: string. The name of a relation of extra-binding
88+ :return: string. The primary IP address for the named binding
89+ :raise: NotImplementedError if run on Juju < 2.0
90+ '''
91+ cmd = ['network-get', '--primary-address', binding]
92+ return subprocess.check_output(cmd).strip()
93
94=== modified file 'charmhelpers/core/host.py'
95--- charmhelpers/core/host.py 2016-01-11 18:16:28 +0000
96+++ charmhelpers/core/host.py 2016-06-10 10:55:43 +0000
97@@ -30,6 +30,8 @@
98 import string
99 import subprocess
100 import hashlib
101+import functools
102+import itertools
103 from contextlib import contextmanager
104 from collections import OrderedDict
105
106@@ -126,22 +128,31 @@
107 return subprocess.call(cmd) == 0
108
109
110+_UPSTART_CONF = "/etc/init/{}.conf"
111+_INIT_D_CONF = "/etc/init.d/{}"
112+
113+
114 def service_running(service_name):
115 """Determine whether a system service is running"""
116 if init_is_systemd():
117 return service('is-active', service_name)
118 else:
119- try:
120- output = subprocess.check_output(
121- ['service', service_name, 'status'],
122- stderr=subprocess.STDOUT).decode('UTF-8')
123- except subprocess.CalledProcessError:
124- return False
125- else:
126- if ("start/running" in output or "is running" in output):
127- return True
128- else:
129+ if os.path.exists(_UPSTART_CONF.format(service_name)):
130+ try:
131+ output = subprocess.check_output(
132+ ['status', service_name],
133+ stderr=subprocess.STDOUT).decode('UTF-8')
134+ except subprocess.CalledProcessError:
135 return False
136+ else:
137+ # This works for upstart scripts where the 'service' command
138+ # returns a consistent string to represent running 'start/running'
139+ if "start/running" in output:
140+ return True
141+ elif os.path.exists(_INIT_D_CONF.format(service_name)):
142+ # Check System V scripts init script return codes
143+ return service('status', service_name)
144+ return False
145
146
147 def service_available(service_name):
148@@ -160,13 +171,13 @@
149
150
151 def init_is_systemd():
152+ """Return True if the host system uses systemd, False otherwise."""
153 return os.path.isdir(SYSTEMD_SYSTEM)
154
155
156 def adduser(username, password=None, shell='/bin/bash', system_user=False,
157- primary_group=None, secondary_groups=None):
158- """
159- Add a user to the system.
160+ primary_group=None, secondary_groups=None, uid=None):
161+ """Add a user to the system.
162
163 Will log but otherwise succeed if the user already exists.
164
165@@ -174,17 +185,23 @@
166 :param str password: Password for user; if ``None``, create a system user
167 :param str shell: The default shell for the user
168 :param bool system_user: Whether to create a login or system user
169- :param str primary_group: Primary group for user; defaults to their username
170+ :param str primary_group: Primary group for user; defaults to username
171 :param list secondary_groups: Optional list of additional groups
172+ :param int uid: UID for user being created
173
174 :returns: The password database entry struct, as returned by `pwd.getpwnam`
175 """
176 try:
177 user_info = pwd.getpwnam(username)
178 log('user {0} already exists!'.format(username))
179+ if uid:
180+ user_info = pwd.getpwuid(int(uid))
181+ log('user with uid {0} already exists!'.format(uid))
182 except KeyError:
183 log('creating user {0}'.format(username))
184 cmd = ['useradd']
185+ if uid:
186+ cmd.extend(['--uid', str(uid)])
187 if system_user or password is None:
188 cmd.append('--system')
189 else:
190@@ -219,14 +236,58 @@
191 return user_exists
192
193
194-def add_group(group_name, system_group=False):
195- """Add a group to the system"""
196+def uid_exists(uid):
197+ """Check if a uid exists"""
198+ try:
199+ pwd.getpwuid(uid)
200+ uid_exists = True
201+ except KeyError:
202+ uid_exists = False
203+ return uid_exists
204+
205+
206+def group_exists(groupname):
207+ """Check if a group exists"""
208+ try:
209+ grp.getgrnam(groupname)
210+ group_exists = True
211+ except KeyError:
212+ group_exists = False
213+ return group_exists
214+
215+
216+def gid_exists(gid):
217+ """Check if a gid exists"""
218+ try:
219+ grp.getgrgid(gid)
220+ gid_exists = True
221+ except KeyError:
222+ gid_exists = False
223+ return gid_exists
224+
225+
226+def add_group(group_name, system_group=False, gid=None):
227+ """Add a group to the system
228+
229+ Will log but otherwise succeed if the group already exists.
230+
231+ :param str group_name: group to create
232+ :param bool system_group: Create system group
233+ :param int gid: GID for user being created
234+
235+ :returns: The password database entry struct, as returned by `grp.getgrnam`
236+ """
237 try:
238 group_info = grp.getgrnam(group_name)
239 log('group {0} already exists!'.format(group_name))
240+ if gid:
241+ group_info = grp.getgrgid(gid)
242+ log('group with gid {0} already exists!'.format(gid))
243 except KeyError:
244 log('creating group {0}'.format(group_name))
245 cmd = ['addgroup']
246+ if gid:
247+ cmd.extend(['--gid', str(gid)])
248 if system_group:
249 cmd.append('--system')
250 else:
251@@ -300,14 +361,12 @@
252
253
254 def fstab_remove(mp):
255- """Remove the given mountpoint entry from /etc/fstab
256- """
257+ """Remove the given mountpoint entry from /etc/fstab"""
258 return Fstab.remove_by_mountpoint(mp)
259
260
261 def fstab_add(dev, mp, fs, options=None):
262- """Adds the given device entry to the /etc/fstab file
263- """
264+ """Adds the given device entry to the /etc/fstab file"""
265 return Fstab.add(dev, mp, fs, options=options)
266
267
268@@ -363,8 +422,7 @@
269
270
271 def file_hash(path, hash_type='md5'):
272- """
273- Generate a hash checksum of the contents of 'path' or None if not found.
274+ """Generate a hash checksum of the contents of 'path' or None if not found.
275
276 :param str hash_type: Any hash alrgorithm supported by :mod:`hashlib`,
277 such as md5, sha1, sha256, sha512, etc.
278@@ -379,10 +437,9 @@
279
280
281 def path_hash(path):
282- """
283- Generate a hash checksum of all files matching 'path'. Standard wildcards
284- like '*' and '?' are supported, see documentation for the 'glob' module for
285- more information.
286+ """Generate a hash checksum of all files matching 'path'. Standard
287+ wildcards like '*' and '?' are supported, see documentation for the 'glob'
288+ module for more information.
289
290 :return: dict: A { filename: hash } dictionary for all matched files.
291 Empty if none found.
292@@ -394,8 +451,7 @@
293
294
295 def check_hash(path, checksum, hash_type='md5'):
296- """
297- Validate a file using a cryptographic checksum.
298+ """Validate a file using a cryptographic checksum.
299
300 :param str checksum: Value of the checksum used to validate the file.
301 :param str hash_type: Hash algorithm used to generate `checksum`.
302@@ -410,10 +466,11 @@
303
304
305 class ChecksumError(ValueError):
306+ """A class derived from Value error to indicate the checksum failed."""
307 pass
308
309
310-def restart_on_change(restart_map, stopstart=False):
311+def restart_on_change(restart_map, stopstart=False, restart_functions=None):
312 """Restart services based on configuration files changing
313
314 This function is used a decorator, for example::
315@@ -431,27 +488,58 @@
316 restarted if any file matching the pattern got changed, created
317 or removed. Standard wildcards are supported, see documentation
318 for the 'glob' module for more information.
319+
320+ @param restart_map: {path_file_name: [service_name, ...]
321+ @param stopstart: DEFAULT false; whether to stop, start OR restart
322+ @param restart_functions: nonstandard functions to use to restart services
323+ {svc: func, ...}
324+ @returns result from decorated function
325 """
326 def wrap(f):
327+ @functools.wraps(f)
328 def wrapped_f(*args, **kwargs):
329- checksums = {path: path_hash(path) for path in restart_map}
330- f(*args, **kwargs)
331- restarts = []
332- for path in restart_map:
333- if path_hash(path) != checksums[path]:
334- restarts += restart_map[path]
335- services_list = list(OrderedDict.fromkeys(restarts))
336- if not stopstart:
337- for service_name in services_list:
338- service('restart', service_name)
339- else:
340- for action in ['stop', 'start']:
341- for service_name in services_list:
342- service(action, service_name)
343+ return restart_on_change_helper(
344+ (lambda: f(*args, **kwargs)), restart_map, stopstart,
345+ restart_functions)
346 return wrapped_f
347 return wrap
348
349
350+def restart_on_change_helper(lambda_f, restart_map, stopstart=False,
351+ restart_functions=None):
352+ """Helper function to perform the restart_on_change function.
353+
354+ This is provided for decorators to restart services if files described
355+ in the restart_map have changed after an invocation of lambda_f().
356+
357+ @param lambda_f: function to call.
358+ @param restart_map: {file: [service, ...]}
359+ @param stopstart: whether to stop, start or restart a service
360+ @param restart_functions: nonstandard functions to use to restart services
361+ {svc: func, ...}
362+ @returns result of lambda_f()
363+ """
364+ if restart_functions is None:
365+ restart_functions = {}
366+ checksums = {path: path_hash(path) for path in restart_map}
367+ r = lambda_f()
368+ # create a list of lists of the services to restart
369+ restarts = [restart_map[path]
370+ for path in restart_map
371+ if path_hash(path) != checksums[path]]
372+ # create a flat list of ordered services without duplicates from lists
373+ services_list = list(OrderedDict.fromkeys(itertools.chain(*restarts)))
374+ if services_list:
375+ actions = ('stop', 'start') if stopstart else ('restart',)
376+ for service_name in services_list:
377+ if service_name in restart_functions:
378+ restart_functions[service_name](service_name)
379+ else:
380+ for action in actions:
381+ service(action, service_name)
382+ return r
383+
384+
385 def lsb_release():
386 """Return /etc/lsb-release in a dict"""
387 d = {}
388@@ -515,7 +603,7 @@
389
390
391 def list_nics(nic_type=None):
392- '''Return a list of nics of given type(s)'''
393+ """Return a list of nics of given type(s)"""
394 if isinstance(nic_type, six.string_types):
395 int_types = [nic_type]
396 else:
397@@ -557,12 +645,13 @@
398
399
400 def set_nic_mtu(nic, mtu):
401- '''Set MTU on a network interface'''
402+ """Set the Maximum Transmission Unit (MTU) on a network interface."""
403 cmd = ['ip', 'link', 'set', nic, 'mtu', mtu]
404 subprocess.check_call(cmd)
405
406
407 def get_nic_mtu(nic):
408+ """Return the Maximum Transmission Unit (MTU) for a network interface."""
409 cmd = ['ip', 'addr', 'show', nic]
410 ip_output = subprocess.check_output(cmd).decode('UTF-8').split('\n')
411 mtu = ""
412@@ -574,6 +663,7 @@
413
414
415 def get_nic_hwaddr(nic):
416+ """Return the Media Access Control (MAC) for a network interface."""
417 cmd = ['ip', '-o', '-0', 'addr', 'show', nic]
418 ip_output = subprocess.check_output(cmd).decode('UTF-8')
419 hwaddr = ""
420@@ -584,7 +674,7 @@
421
422
423 def cmp_pkgrevno(package, revno, pkgcache=None):
424- '''Compare supplied revno with the revno of the installed package
425+ """Compare supplied revno with the revno of the installed package
426
427 * 1 => Installed revno is greater than supplied arg
428 * 0 => Installed revno is the same as supplied arg
429@@ -593,7 +683,7 @@
430 This function imports apt_cache function from charmhelpers.fetch if
431 the pkgcache argument is None. Be sure to add charmhelpers.fetch if
432 you call this function, or pass an apt_pkg.Cache() instance.
433- '''
434+ """
435 import apt_pkg
436 if not pkgcache:
437 from charmhelpers.fetch import apt_cache
438@@ -603,19 +693,27 @@
439
440
441 @contextmanager
442-def chdir(d):
443+def chdir(directory):
444+ """Change the current working directory to a different directory for a code
445+ block and return the previous directory after the block exits. Useful to
446+ run commands from a specificed directory.
447+
448+ :param str directory: The directory path to change to for this context.
449+ """
450 cur = os.getcwd()
451 try:
452- yield os.chdir(d)
453+ yield os.chdir(directory)
454 finally:
455 os.chdir(cur)
456
457
458 def chownr(path, owner, group, follow_links=True, chowntopdir=False):
459- """
460- Recursively change user and group ownership of files and directories
461+ """Recursively change user and group ownership of files and directories
462 in given path. Doesn't chown path itself by default, only its children.
463
464+ :param str path: The string path to start changing ownership.
465+ :param str owner: The owner string to use when looking up the uid.
466+ :param str group: The group string to use when looking up the gid.
467 :param bool follow_links: Also Chown links if True
468 :param bool chowntopdir: Also chown path itself if True
469 """
470@@ -639,15 +737,23 @@
471
472
473 def lchownr(path, owner, group):
474+ """Recursively change user and group ownership of files and directories
475+ in a given path, not following symbolic links. See the documentation for
476+ 'os.lchown' for more information.
477+
478+ :param str path: The string path to start changing ownership.
479+ :param str owner: The owner string to use when looking up the uid.
480+ :param str group: The group string to use when looking up the gid.
481+ """
482 chownr(path, owner, group, follow_links=False)
483
484
485 def get_total_ram():
486- '''The total amount of system RAM in bytes.
487+ """The total amount of system RAM in bytes.
488
489 This is what is reported by the OS, and may be overcommitted when
490 there are multiple containers hosted on the same machine.
491- '''
492+ """
493 with open('/proc/meminfo', 'r') as f:
494 for line in f.readlines():
495 if line:
496
497=== modified file 'charmhelpers/fetch/__init__.py'
498--- charmhelpers/fetch/__init__.py 2016-01-11 18:16:28 +0000
499+++ charmhelpers/fetch/__init__.py 2016-06-10 10:55:43 +0000
500@@ -106,6 +106,14 @@
501 'mitaka/proposed': 'trusty-proposed/mitaka',
502 'trusty-mitaka/proposed': 'trusty-proposed/mitaka',
503 'trusty-proposed/mitaka': 'trusty-proposed/mitaka',
504+ # Newton
505+ 'newton': 'xenial-updates/newton',
506+ 'xenial-newton': 'xenial-updates/newton',
507+ 'xenial-newton/updates': 'xenial-updates/newton',
508+ 'xenial-updates/newton': 'xenial-updates/newton',
509+ 'newton/proposed': 'xenial-proposed/newton',
510+ 'xenial-newton/proposed': 'xenial-proposed/newton',
511+ 'xenial-proposed/newton': 'xenial-proposed/newton',
512 }
513
514 # The order of this list is very important. Handlers should be listed in from
515
516=== modified file 'charmhelpers/fetch/giturl.py'
517--- charmhelpers/fetch/giturl.py 2016-01-11 18:16:28 +0000
518+++ charmhelpers/fetch/giturl.py 2016-06-10 10:55:43 +0000
519@@ -15,7 +15,7 @@
520 # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
521
522 import os
523-from subprocess import check_call
524+from subprocess import check_call, CalledProcessError
525 from charmhelpers.fetch import (
526 BaseFetchHandler,
527 UnhandledSource,
528@@ -63,6 +63,8 @@
529 branch_name)
530 try:
531 self.clone(source, dest_dir, branch, depth)
532+ except CalledProcessError as e:
533+ raise UnhandledSource(e)
534 except OSError as e:
535 raise UnhandledSource(e.strerror)
536 return dest_dir

Subscribers

People subscribed via source and target branches

to all changes: