Merge lp:~lazypower/charms/trusty/mongodb/fixing_st00f into lp:charms/mongodb

Proposed by Charles Butler
Status: Merged
Merged at revision: 48
Proposed branch: lp:~lazypower/charms/trusty/mongodb/fixing_st00f
Merge into: lp:charms/mongodb
Diff against target: 1251 lines (+472/-340)
9 files modified
.bzrignore (+3/-0)
Makefile (+9/-1)
hooks/charmhelpers/core/fstab.py (+116/-0)
hooks/charmhelpers/core/hookenv.py (+103/-5)
hooks/charmhelpers/core/host.py (+38/-8)
hooks/charmhelpers/fetch/__init__.py (+130/-81)
hooks/charmhelpers/fetch/bzrurl.py (+2/-1)
hooks/hooks.py (+66/-242)
metadata.yaml (+5/-2)
To merge this branch: bzr merge lp:~lazypower/charms/trusty/mongodb/fixing_st00f
Reviewer Review Type Date Requested Status
Juan L. Negron (community) Approve
charmers Pending
Kapil Thangavelu Pending
Review via email: mp+226357@code.launchpad.net

Description of the change

Migrated to use of charm-helpers, greenlights charms, still WIP wrt refactoring out a common contrib module for charmhelpers.

To post a comment you must log in.
Revision history for this message
Kapil Thangavelu (hazmat) wrote :

lgtm, i haven't run the charm though, and i'd yank the sync script copies though.

49. By Charles Butler

Updates to bzrignore

50. By Charles Butler

Removes the sync scripts

Revision history for this message
Juan L. Negron (negronjl) wrote :

LGTM... merging in a minute.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file '.bzrignore'
2--- .bzrignore 1970-01-01 00:00:00 +0000
3+++ .bzrignore 2014-07-10 18:39:06 +0000
4@@ -0,0 +1,3 @@
5+.git
6+bin/*
7+scripts/charm-helpers-sync.py
8
9=== modified file 'Makefile'
10--- Makefile 2014-04-11 20:55:42 +0000
11+++ Makefile 2014-07-10 18:39:06 +0000
12@@ -13,9 +13,17 @@
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16+PYTHON := /usr/bin/env python
17
18 unittest:
19 tests/10-unit.test
20
21 sync:
22- @charm-helper-sync -c charm-helpers-sync.yaml
23+ @mkdir -p bin
24+ @bzr cat lp:charm-helpers/tools/charm_helpers_sync/charm_helpers_sync.py > bin/charm_helpers_sync.py
25+ @$(PYTHON) bin/charm_helpers_sync.py -c charm-helpers-sync.yaml
26+
27+clean:
28+ @find . -name \*.pyc -delete
29+ @find . -name '*.bak' -delete
30+
31
32=== added file '__init__.py'
33=== added directory 'bin'
34=== added file 'hooks/charmhelpers/core/fstab.py'
35--- hooks/charmhelpers/core/fstab.py 1970-01-01 00:00:00 +0000
36+++ hooks/charmhelpers/core/fstab.py 2014-07-10 18:39:06 +0000
37@@ -0,0 +1,116 @@
38+#!/usr/bin/env python
39+# -*- coding: utf-8 -*-
40+
41+__author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>'
42+
43+import os
44+
45+
46+class Fstab(file):
47+ """This class extends file in order to implement a file reader/writer
48+ for file `/etc/fstab`
49+ """
50+
51+ class Entry(object):
52+ """Entry class represents a non-comment line on the `/etc/fstab` file
53+ """
54+ def __init__(self, device, mountpoint, filesystem,
55+ options, d=0, p=0):
56+ self.device = device
57+ self.mountpoint = mountpoint
58+ self.filesystem = filesystem
59+
60+ if not options:
61+ options = "defaults"
62+
63+ self.options = options
64+ self.d = d
65+ self.p = p
66+
67+ def __eq__(self, o):
68+ return str(self) == str(o)
69+
70+ def __str__(self):
71+ return "{} {} {} {} {} {}".format(self.device,
72+ self.mountpoint,
73+ self.filesystem,
74+ self.options,
75+ self.d,
76+ self.p)
77+
78+ DEFAULT_PATH = os.path.join(os.path.sep, 'etc', 'fstab')
79+
80+ def __init__(self, path=None):
81+ if path:
82+ self._path = path
83+ else:
84+ self._path = self.DEFAULT_PATH
85+ file.__init__(self, self._path, 'r+')
86+
87+ def _hydrate_entry(self, line):
88+ # NOTE: use split with no arguments to split on any
89+ # whitespace including tabs
90+ return Fstab.Entry(*filter(
91+ lambda x: x not in ('', None),
92+ line.strip("\n").split()))
93+
94+ @property
95+ def entries(self):
96+ self.seek(0)
97+ for line in self.readlines():
98+ try:
99+ if not line.startswith("#"):
100+ yield self._hydrate_entry(line)
101+ except ValueError:
102+ pass
103+
104+ def get_entry_by_attr(self, attr, value):
105+ for entry in self.entries:
106+ e_attr = getattr(entry, attr)
107+ if e_attr == value:
108+ return entry
109+ return None
110+
111+ def add_entry(self, entry):
112+ if self.get_entry_by_attr('device', entry.device):
113+ return False
114+
115+ self.write(str(entry) + '\n')
116+ self.truncate()
117+ return entry
118+
119+ def remove_entry(self, entry):
120+ self.seek(0)
121+
122+ lines = self.readlines()
123+
124+ found = False
125+ for index, line in enumerate(lines):
126+ if not line.startswith("#"):
127+ if self._hydrate_entry(line) == entry:
128+ found = True
129+ break
130+
131+ if not found:
132+ return False
133+
134+ lines.remove(line)
135+
136+ self.seek(0)
137+ self.write(''.join(lines))
138+ self.truncate()
139+ return True
140+
141+ @classmethod
142+ def remove_by_mountpoint(cls, mountpoint, path=None):
143+ fstab = cls(path=path)
144+ entry = fstab.get_entry_by_attr('mountpoint', mountpoint)
145+ if entry:
146+ return fstab.remove_entry(entry)
147+ return False
148+
149+ @classmethod
150+ def add(cls, device, mountpoint, filesystem, options=None, path=None):
151+ return cls(path=path).add_entry(Fstab.Entry(device,
152+ mountpoint, filesystem,
153+ options=options))
154
155=== modified file 'hooks/charmhelpers/core/hookenv.py'
156--- hooks/charmhelpers/core/hookenv.py 2014-04-11 20:55:42 +0000
157+++ hooks/charmhelpers/core/hookenv.py 2014-07-10 18:39:06 +0000
158@@ -25,7 +25,7 @@
159 def cached(func):
160 """Cache return values for multiple executions of func + args
161
162- For example:
163+ For example::
164
165 @cached
166 def unit_get(attribute):
167@@ -155,6 +155,100 @@
168 return os.path.basename(sys.argv[0])
169
170
171+class Config(dict):
172+ """A Juju charm config dictionary that can write itself to
173+ disk (as json) and track which values have changed since
174+ the previous hook invocation.
175+
176+ Do not instantiate this object directly - instead call
177+ ``hookenv.config()``
178+
179+ Example usage::
180+
181+ >>> # inside a hook
182+ >>> from charmhelpers.core import hookenv
183+ >>> config = hookenv.config()
184+ >>> config['foo']
185+ 'bar'
186+ >>> config['mykey'] = 'myval'
187+ >>> config.save()
188+
189+
190+ >>> # user runs `juju set mycharm foo=baz`
191+ >>> # now we're inside subsequent config-changed hook
192+ >>> config = hookenv.config()
193+ >>> config['foo']
194+ 'baz'
195+ >>> # test to see if this val has changed since last hook
196+ >>> config.changed('foo')
197+ True
198+ >>> # what was the previous value?
199+ >>> config.previous('foo')
200+ 'bar'
201+ >>> # keys/values that we add are preserved across hooks
202+ >>> config['mykey']
203+ 'myval'
204+ >>> # don't forget to save at the end of hook!
205+ >>> config.save()
206+
207+ """
208+ CONFIG_FILE_NAME = '.juju-persistent-config'
209+
210+ def __init__(self, *args, **kw):
211+ super(Config, self).__init__(*args, **kw)
212+ self._prev_dict = None
213+ self.path = os.path.join(charm_dir(), Config.CONFIG_FILE_NAME)
214+ if os.path.exists(self.path):
215+ self.load_previous()
216+
217+ def load_previous(self, path=None):
218+ """Load previous copy of config from disk so that current values
219+ can be compared to previous values.
220+
221+ :param path:
222+
223+ File path from which to load the previous config. If `None`,
224+ config is loaded from the default location. If `path` is
225+ specified, subsequent `save()` calls will write to the same
226+ path.
227+
228+ """
229+ self.path = path or self.path
230+ with open(self.path) as f:
231+ self._prev_dict = json.load(f)
232+
233+ def changed(self, key):
234+ """Return true if the value for this key has changed since
235+ the last save.
236+
237+ """
238+ if self._prev_dict is None:
239+ return True
240+ return self.previous(key) != self.get(key)
241+
242+ def previous(self, key):
243+ """Return previous value for this key, or None if there
244+ is no "previous" value.
245+
246+ """
247+ if self._prev_dict:
248+ return self._prev_dict.get(key)
249+ return None
250+
251+ def save(self):
252+ """Save this config to disk.
253+
254+ Preserves items in _prev_dict that do not exist in self.
255+
256+ """
257+ if self._prev_dict:
258+ for k, v in self._prev_dict.iteritems():
259+ if k not in self:
260+ self[k] = v
261+ with open(self.path, 'w') as f:
262+ json.dump(self, f)
263+
264+
265 @cached
266 def config(scope=None):
267 """Juju charm configuration"""
268@@ -163,7 +257,10 @@
269 config_cmd_line.append(scope)
270 config_cmd_line.append('--format=json')
271 try:
272- return json.loads(subprocess.check_output(config_cmd_line))
273+ config_data = json.loads(subprocess.check_output(config_cmd_line))
274+ if scope is not None:
275+ return config_data
276+ return Config(config_data)
277 except ValueError:
278 return None
279
280@@ -348,18 +445,19 @@
281 class Hooks(object):
282 """A convenient handler for hook functions.
283
284- Example:
285+ Example::
286+
287 hooks = Hooks()
288
289 # register a hook, taking its name from the function name
290 @hooks.hook()
291 def install():
292- ...
293+ pass # your code here
294
295 # register a hook, providing a custom hook name
296 @hooks.hook("config-changed")
297 def config_changed():
298- ...
299+ pass # your code here
300
301 if __name__ == "__main__":
302 # execute a hook based on the name the program is called by
303
304=== modified file 'hooks/charmhelpers/core/host.py'
305--- hooks/charmhelpers/core/host.py 2014-04-11 20:55:42 +0000
306+++ hooks/charmhelpers/core/host.py 2014-07-10 18:39:06 +0000
307@@ -16,6 +16,7 @@
308 from collections import OrderedDict
309
310 from hookenv import log
311+from fstab import Fstab
312
313
314 def service_start(service_name):
315@@ -34,7 +35,8 @@
316
317
318 def service_reload(service_name, restart_on_failure=False):
319- """Reload a system service, optionally falling back to restart if reload fails"""
320+ """Reload a system service, optionally falling back to restart if
321+ reload fails"""
322 service_result = service('reload', service_name)
323 if not service_result and restart_on_failure:
324 service_result = service('restart', service_name)
325@@ -143,7 +145,19 @@
326 target.write(content)
327
328
329-def mount(device, mountpoint, options=None, persist=False):
330+def fstab_remove(mp):
331+ """Remove the given mountpoint entry from /etc/fstab
332+ """
333+ return Fstab.remove_by_mountpoint(mp)
334+
335+
336+def fstab_add(dev, mp, fs, options=None):
337+ """Adds the given device entry to the /etc/fstab file
338+ """
339+ return Fstab.add(dev, mp, fs, options=options)
340+
341+
342+def mount(device, mountpoint, options=None, persist=False, filesystem="ext3"):
343 """Mount a filesystem at a particular mountpoint"""
344 cmd_args = ['mount']
345 if options is not None:
346@@ -154,9 +168,9 @@
347 except subprocess.CalledProcessError, e:
348 log('Error mounting {} at {}\n{}'.format(device, mountpoint, e.output))
349 return False
350+
351 if persist:
352- # TODO: update fstab
353- pass
354+ return fstab_add(device, mountpoint, filesystem, options=options)
355 return True
356
357
358@@ -168,9 +182,9 @@
359 except subprocess.CalledProcessError, e:
360 log('Error unmounting {}\n{}'.format(mountpoint, e.output))
361 return False
362+
363 if persist:
364- # TODO: update fstab
365- pass
366+ return fstab_remove(mountpoint)
367 return True
368
369
370@@ -197,13 +211,13 @@
371 def restart_on_change(restart_map, stopstart=False):
372 """Restart services based on configuration files changing
373
374- This function is used a decorator, for example
375+ This function is used a decorator, for example::
376
377 @restart_on_change({
378 '/etc/ceph/ceph.conf': [ 'cinder-api', 'cinder-volume' ]
379 })
380 def ceph_client_changed():
381- ...
382+ pass # your code here
383
384 In this example, the cinder-api and cinder-volume services
385 would be restarted if /etc/ceph/ceph.conf is changed by the
386@@ -295,3 +309,19 @@
387 if 'link/ether' in words:
388 hwaddr = words[words.index('link/ether') + 1]
389 return hwaddr
390+
391+
392+def cmp_pkgrevno(package, revno, pkgcache=None):
393+ '''Compare supplied revno with the revno of the installed package
394+
395+ * 1 => Installed revno is greater than supplied arg
396+ * 0 => Installed revno is the same as supplied arg
397+ * -1 => Installed revno is less than supplied arg
398+
399+ '''
400+ import apt_pkg
401+ if not pkgcache:
402+ apt_pkg.init()
403+ pkgcache = apt_pkg.Cache()
404+ pkg = pkgcache[package]
405+ return apt_pkg.version_compare(pkg.current_ver.ver_str, revno)
406
407=== modified file 'hooks/charmhelpers/fetch/__init__.py'
408--- hooks/charmhelpers/fetch/__init__.py 2014-04-11 20:55:42 +0000
409+++ hooks/charmhelpers/fetch/__init__.py 2014-07-10 18:39:06 +0000
410@@ -1,4 +1,5 @@
411 import importlib
412+import time
413 from yaml import safe_load
414 from charmhelpers.core.host import (
415 lsb_release
416@@ -12,9 +13,9 @@
417 config,
418 log,
419 )
420-import apt_pkg
421 import os
422
423+
424 CLOUD_ARCHIVE = """# Ubuntu Cloud Archive
425 deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main
426 """
427@@ -54,12 +55,74 @@
428 'icehouse/proposed': 'precise-proposed/icehouse',
429 'precise-icehouse/proposed': 'precise-proposed/icehouse',
430 'precise-proposed/icehouse': 'precise-proposed/icehouse',
431+ # Juno
432+ 'juno': 'trusty-updates/juno',
433+ 'trusty-juno': 'trusty-updates/juno',
434+ 'trusty-juno/updates': 'trusty-updates/juno',
435+ 'trusty-updates/juno': 'trusty-updates/juno',
436+ 'juno/proposed': 'trusty-proposed/juno',
437+ 'juno/proposed': 'trusty-proposed/juno',
438+ 'trusty-juno/proposed': 'trusty-proposed/juno',
439+ 'trusty-proposed/juno': 'trusty-proposed/juno',
440 }
441
442+# The order of this list is very important. Handlers should be listed in from
443+# least- to most-specific URL matching.
444+FETCH_HANDLERS = (
445+ 'charmhelpers.fetch.archiveurl.ArchiveUrlFetchHandler',
446+ 'charmhelpers.fetch.bzrurl.BzrUrlFetchHandler',
447+)
448+
449+APT_NO_LOCK = 100 # The return code for "couldn't acquire lock" in APT.
450+APT_NO_LOCK_RETRY_DELAY = 10 # Wait 10 seconds between apt lock checks.
451+APT_NO_LOCK_RETRY_COUNT = 30 # Retry to acquire the lock X times.
452+
453+
454+class SourceConfigError(Exception):
455+ pass
456+
457+
458+class UnhandledSource(Exception):
459+ pass
460+
461+
462+class AptLockError(Exception):
463+ pass
464+
465+
466+class BaseFetchHandler(object):
467+
468+ """Base class for FetchHandler implementations in fetch plugins"""
469+
470+ def can_handle(self, source):
471+ """Returns True if the source can be handled. Otherwise returns
472+ a string explaining why it cannot"""
473+ return "Wrong source type"
474+
475+ def install(self, source):
476+ """Try to download and unpack the source. Return the path to the
477+ unpacked files or raise UnhandledSource."""
478+ raise UnhandledSource("Wrong source type {}".format(source))
479+
480+ def parse_url(self, url):
481+ return urlparse(url)
482+
483+ def base_url(self, url):
484+ """Return url without querystring or fragment"""
485+ parts = list(self.parse_url(url))
486+ parts[4:] = ['' for i in parts[4:]]
487+ return urlunparse(parts)
488+
489
490 def filter_installed_packages(packages):
491 """Returns a list of packages that require installation"""
492+ import apt_pkg
493 apt_pkg.init()
494+
495+ # Tell apt to build an in-memory cache to prevent race conditions (if
496+ # another process is already building the cache).
497+ apt_pkg.config.set("Dir::Cache::pkgcache", "")
498+
499 cache = apt_pkg.Cache()
500 _pkgs = []
501 for package in packages:
502@@ -87,14 +150,7 @@
503 cmd.extend(packages)
504 log("Installing {} with options: {}".format(packages,
505 options))
506- env = os.environ.copy()
507- if 'DEBIAN_FRONTEND' not in env:
508- env['DEBIAN_FRONTEND'] = 'noninteractive'
509-
510- if fatal:
511- subprocess.check_call(cmd, env=env)
512- else:
513- subprocess.call(cmd, env=env)
514+ _run_apt_command(cmd, fatal)
515
516
517 def apt_upgrade(options=None, fatal=False, dist=False):
518@@ -109,24 +165,13 @@
519 else:
520 cmd.append('upgrade')
521 log("Upgrading with options: {}".format(options))
522-
523- env = os.environ.copy()
524- if 'DEBIAN_FRONTEND' not in env:
525- env['DEBIAN_FRONTEND'] = 'noninteractive'
526-
527- if fatal:
528- subprocess.check_call(cmd, env=env)
529- else:
530- subprocess.call(cmd, env=env)
531+ _run_apt_command(cmd, fatal)
532
533
534 def apt_update(fatal=False):
535 """Update local apt cache"""
536 cmd = ['apt-get', 'update']
537- if fatal:
538- subprocess.check_call(cmd)
539- else:
540- subprocess.call(cmd)
541+ _run_apt_command(cmd, fatal)
542
543
544 def apt_purge(packages, fatal=False):
545@@ -137,10 +182,7 @@
546 else:
547 cmd.extend(packages)
548 log("Purging {}".format(packages))
549- if fatal:
550- subprocess.check_call(cmd)
551- else:
552- subprocess.call(cmd)
553+ _run_apt_command(cmd, fatal)
554
555
556 def apt_hold(packages, fatal=False):
557@@ -151,6 +193,7 @@
558 else:
559 cmd.extend(packages)
560 log("Holding {}".format(packages))
561+
562 if fatal:
563 subprocess.check_call(cmd)
564 else:
565@@ -184,57 +227,50 @@
566 apt.write(PROPOSED_POCKET.format(release))
567 if key:
568 subprocess.check_call(['apt-key', 'adv', '--keyserver',
569- 'keyserver.ubuntu.com', '--recv',
570+ 'hkp://keyserver.ubuntu.com:80', '--recv',
571 key])
572
573
574-class SourceConfigError(Exception):
575- pass
576-
577-
578 def configure_sources(update=False,
579 sources_var='install_sources',
580 keys_var='install_keys'):
581 """
582- Configure multiple sources from charm configuration
583+ Configure multiple sources from charm configuration.
584+
585+ The lists are encoded as yaml fragments in the configuration.
586+ The frament needs to be included as a string.
587
588 Example config:
589- install_sources:
590+ install_sources: |
591 - "ppa:foo"
592 - "http://example.com/repo precise main"
593- install_keys:
594+ install_keys: |
595 - null
596 - "a1b2c3d4"
597
598 Note that 'null' (a.k.a. None) should not be quoted.
599 """
600- sources = safe_load(config(sources_var))
601- keys = config(keys_var)
602- if keys is not None:
603- keys = safe_load(keys)
604- if isinstance(sources, basestring) and (
605- keys is None or isinstance(keys, basestring)):
606- add_source(sources, keys)
607+ sources = safe_load((config(sources_var) or '').strip()) or []
608+ keys = safe_load((config(keys_var) or '').strip()) or None
609+
610+ if isinstance(sources, basestring):
611+ sources = [sources]
612+
613+ if keys is None:
614+ for source in sources:
615+ add_source(source, None)
616 else:
617- if not len(sources) == len(keys):
618- msg = 'Install sources and keys lists are different lengths'
619- raise SourceConfigError(msg)
620- for src_num in range(len(sources)):
621- add_source(sources[src_num], keys[src_num])
622+ if isinstance(keys, basestring):
623+ keys = [keys]
624+
625+ if len(sources) != len(keys):
626+ raise SourceConfigError(
627+ 'Install sources and keys lists are different lengths')
628+ for source, key in zip(sources, keys):
629+ add_source(source, key)
630 if update:
631 apt_update(fatal=True)
632
633-# The order of this list is very important. Handlers should be listed in from
634-# least- to most-specific URL matching.
635-FETCH_HANDLERS = (
636- 'charmhelpers.fetch.archiveurl.ArchiveUrlFetchHandler',
637- 'charmhelpers.fetch.bzrurl.BzrUrlFetchHandler',
638-)
639-
640-
641-class UnhandledSource(Exception):
642- pass
643-
644
645 def install_remote(source):
646 """
647@@ -265,30 +301,6 @@
648 return install_remote(source)
649
650
651-class BaseFetchHandler(object):
652-
653- """Base class for FetchHandler implementations in fetch plugins"""
654-
655- def can_handle(self, source):
656- """Returns True if the source can be handled. Otherwise returns
657- a string explaining why it cannot"""
658- return "Wrong source type"
659-
660- def install(self, source):
661- """Try to download and unpack the source. Return the path to the
662- unpacked files or raise UnhandledSource."""
663- raise UnhandledSource("Wrong source type {}".format(source))
664-
665- def parse_url(self, url):
666- return urlparse(url)
667-
668- def base_url(self, url):
669- """Return url without querystring or fragment"""
670- parts = list(self.parse_url(url))
671- parts[4:] = ['' for i in parts[4:]]
672- return urlunparse(parts)
673-
674-
675 def plugins(fetch_handlers=None):
676 if not fetch_handlers:
677 fetch_handlers = FETCH_HANDLERS
678@@ -306,3 +318,40 @@
679 log("FetchHandler {} not found, skipping plugin".format(
680 handler_name))
681 return plugin_list
682+
683+
684+def _run_apt_command(cmd, fatal=False):
685+ """
686+ Run an APT command, checking output and retrying if the fatal flag is set
687+ to True.
688+
689+ :param: cmd: str: The apt command to run.
690+ :param: fatal: bool: Whether the command's output should be checked and
691+ retried.
692+ """
693+ env = os.environ.copy()
694+
695+ if 'DEBIAN_FRONTEND' not in env:
696+ env['DEBIAN_FRONTEND'] = 'noninteractive'
697+
698+ if fatal:
699+ retry_count = 0
700+ result = None
701+
702+ # If the command is considered "fatal", we need to retry if the apt
703+ # lock was not acquired.
704+
705+ while result is None or result == APT_NO_LOCK:
706+ try:
707+ result = subprocess.check_call(cmd, env=env)
708+ except subprocess.CalledProcessError, e:
709+ retry_count = retry_count + 1
710+ if retry_count > APT_NO_LOCK_RETRY_COUNT:
711+ raise
712+ result = e.returncode
713+ log("Couldn't acquire DPKG lock. Will retry in {} seconds."
714+ "".format(APT_NO_LOCK_RETRY_DELAY))
715+ time.sleep(APT_NO_LOCK_RETRY_DELAY)
716+
717+ else:
718+ subprocess.call(cmd, env=env)
719
720=== modified file 'hooks/charmhelpers/fetch/bzrurl.py'
721--- hooks/charmhelpers/fetch/bzrurl.py 2014-04-11 20:55:42 +0000
722+++ hooks/charmhelpers/fetch/bzrurl.py 2014-07-10 18:39:06 +0000
723@@ -39,7 +39,8 @@
724 def install(self, source):
725 url_parts = self.parse_url(source)
726 branch_name = url_parts.path.strip("/").split("/")[-1]
727- dest_dir = os.path.join(os.environ.get('CHARM_DIR'), "fetched", branch_name)
728+ dest_dir = os.path.join(os.environ.get('CHARM_DIR'), "fetched",
729+ branch_name)
730 if not os.path.exists(dest_dir):
731 mkdir(dest_dir, perms=0755)
732 try:
733
734=== modified file 'hooks/hooks.py'
735--- hooks/hooks.py 2014-04-11 21:00:49 +0000
736+++ hooks/hooks.py 2014-07-10 18:39:06 +0000
737@@ -29,8 +29,22 @@
738 apt_update,
739 apt_install
740 )
741+
742 from charmhelpers.core.hookenv import (
743- config
744+ config,
745+ unit_get,
746+ relation_get,
747+ relation_set,
748+ relations_for_id,
749+ relations_of_type,
750+ open_port,
751+ close_port,
752+)
753+
754+from charmhelpers.core.hookenv import log as juju_log
755+
756+from charmhelpers.core.host import (
757+ service,
758 )
759
760
761@@ -47,201 +61,6 @@
762 # Supporting functions
763 ###############################################################################
764
765-
766-#------------------------------------------------------------------------------
767-# juju_log: calls juju-log and records the message defined by the message
768-# variable
769-#------------------------------------------------------------------------------
770-def juju_log(message=None):
771- return (subprocess.call(['juju-log', str(message)]) == 0)
772-
773-
774-#------------------------------------------------------------------------------
775-# service: Analogous to calling service on the command line to start/stop
776-# and get status of a service/daemon.
777-# Parameters:
778-# service_name: The name of the service to act on.
779-# service_action: The action (start, stop, status, etc.)
780-# Returns: True if the command was successfully executed or False on
781-# error.
782-#------------------------------------------------------------------------------
783-def service(service_name=None, service_action=None):
784- juju_log("service: %s, action: %s" % (service_name, service_action))
785- if service_name is not None and service_action is not None:
786- retVal = subprocess.call(
787- ["service", service_name, service_action]) == 0
788- else:
789- retVal = False
790- juju_log("service %s %s returns: %s" %
791- (service_name, service_action, retVal))
792- return(retVal)
793-
794-
795-#------------------------------------------------------------------------------
796-# unit_get: Convenience function wrapping the juju command unit-get
797-# Parameter:
798-# setting_name: The setting to get out of unit_get
799-# Returns: The requested information or None on error
800-#------------------------------------------------------------------------------
801-def unit_get(setting_name=None):
802- juju_log("unit_get: %s" % setting_name)
803- try:
804- cmd_line = ['unit-get', '--format=json']
805- if setting_name is not None:
806- cmd_line.append(setting_name)
807- unit_data = json.loads(subprocess.check_output(cmd_line))
808- except Exception, e:
809- subprocess.call(['juju-log', str(e)])
810- unit_data = None
811- finally:
812- juju_log("unit_get %s returns: %s" % (setting_name, unit_data))
813- return(unit_data)
814-
815-
816-#------------------------------------------------------------------------------
817-# config_get: Returns a dictionary containing all of the config information
818-# Optional parameter: scope
819-# scope: limits the scope of the returned configuration to the
820-# desired config item.
821-#------------------------------------------------------------------------------
822-def config_get(scope=None):
823- juju_log("config_get: %s" % scope)
824- try:
825- config_cmd_line = ['config-get']
826- if scope is not None:
827- config_cmd_line.append(scope)
828- config_cmd_line.append('--format=json')
829- config_data = json.loads(subprocess.check_output(config_cmd_line))
830- except Exception, e:
831- juju_log(str(e))
832- config_data = None
833- finally:
834- juju_log("config_get: %s returns: %s" % (scope, config_data))
835- return(config_data)
836-
837-
838-#------------------------------------------------------------------------------
839-# relation_get: Returns a dictionary containing the relation information
840-# Optional parameters: scope, relation_id
841-# scope: limits the scope of the returned data to the
842-# desired item.
843-# unit_name: limits the data ( and optionally the scope )
844-# to the specified unit
845-# relation_id: specify relation id for out of context usage.
846-#------------------------------------------------------------------------------
847-def relation_get(scope=None, unit_name=None, relation_id=None,
848- wait_for=default_wait_for, max_tries=default_max_tries):
849- juju_log("relation_get: scope: %s, unit_name: %s, relation_id: %s" %
850- (scope, unit_name, relation_id))
851- current_try = 0
852- try:
853- relation_cmd_line = ['relation-get', '--format=json']
854- if relation_id is not None:
855- relation_cmd_line.extend(('-r', relation_id))
856- if scope is not None:
857- relation_cmd_line.append(scope)
858- else:
859- relation_cmd_line.append('')
860- if unit_name is not None:
861- relation_cmd_line.append(unit_name)
862- relation_data = json.loads(subprocess.check_output(relation_cmd_line))
863-
864-# while relation_data is None and current_try < max_tries:
865-# time.sleep(wait_for)
866-# relation_data = json.loads(subprocess.check_output(relation_cmd_line))
867-# current_try += 1
868-
869- except Exception, e:
870- juju_log(str(e))
871- relation_data = None
872- finally:
873- juju_log("relation_get returns: %s" % relation_data)
874- return(relation_data)
875-
876-
877-#------------------------------------------------------------------------------
878-# relation_set: Convenience function wrapping the juju command relation-set
879-# Parameters:
880-# key_value_pairs: A dictionary containing the key/value pairs
881-# to be set.
882-# Optional Parameter:
883-# relation_id: The relation id to use
884-# Returns: True on success or False on failure
885-#------------------------------------------------------------------------------
886-def relation_set(key_value_pairs=None, relation_id=None):
887- juju_log("relation_set: kv: %s, relation_id: %s" %
888- (key_value_pairs, relation_id))
889- if key_value_pairs is None or not isinstance(key_value_pairs, dict):
890- juju_log("relation_set: Invalid key_value_pais.")
891- return(False)
892- try:
893- relation_cmd_line = ['relation-set', '--format=json']
894- if relation_id is not None:
895- relation_cmd_line.append('-r %s' % relation_id)
896- for (key, value) in key_value_pairs.items():
897- relation_cmd_line.append('%s=%s' % (key, value))
898- retVal = (subprocess.call(relation_cmd_line) == 0)
899- except Exception, e:
900- juju_log(str(e))
901- retVal = False
902- finally:
903- juju_log("relation_set returns: %s" % retVal)
904- return(retVal)
905-
906-
907-def relation_list(relation_id=None, wait_for=default_wait_for,
908- max_tries=default_max_tries):
909- juju_log("relation_list: relation_id: %s" % relation_id)
910- current_try = 0
911- try:
912- relation_cmd_line = ['relation-list', '--format=json']
913- if relation_id is not None:
914- relation_cmd_line.append('-r %s' % relation_id)
915- relation_data = json.loads(subprocess.check_output(relation_cmd_line))
916-
917-# while relation_data is None and current_try < max_tries:
918-# time.sleep(wait_for)
919-# relation_data = json.loads(subprocess.check_output(relation_cmd_line))
920-# current_try += 1
921-
922- except Exception, e:
923- juju_log(str(e))
924- relation_data = None
925- finally:
926- juju_log("relation_id %s returns: %s" % (relation_id, relation_data))
927- return(relation_data)
928-
929-
930-#------------------------------------------------------------------------------
931-# open_port: Convenience function to open a port in juju to
932-# expose a service
933-#------------------------------------------------------------------------------
934-def open_port(port=None, protocol="TCP"):
935- juju_log("open_port: port: %d protocol: %s" % (int(port), protocol))
936- if port is None:
937- retVal = False
938- else:
939- retVal = subprocess.call(['open-port', "%d/%s" %
940- (int(port), protocol)]) == 0
941- juju_log("open_port %d/%s returns: %s" % (int(port), protocol, retVal))
942- return(retVal)
943-
944-
945-#------------------------------------------------------------------------------
946-# close_port: Convenience function to close a port in juju to
947-# unexpose a service
948-#------------------------------------------------------------------------------
949-def close_port(port=None, protocol="TCP"):
950- juju_log("close_port: port: %d protocol: %s" % (int(port), protocol))
951- if port is None:
952- retVal = False
953- else:
954- retVal = subprocess.call(['close-port', "%d/%s" %
955- (int(port), protocol)]) == 0
956- juju_log("close_port %d/%s returns: %s" % (int(port), protocol, retVal))
957- return(retVal)
958-
959-
960 def port_check(host=None, port=None, protocol='TCP'):
961 if host is None or port is None:
962 juju_log("port_check: host and port must be defined.")
963@@ -643,7 +462,7 @@
964
965
966 def configsvr_status(wait_for=default_wait_for, max_tries=default_max_tries):
967- config_data = config_get()
968+ config_data = config()
969 current_try = 0
970 while (process_check_pidfile('/var/run/mongodb/configsvr.pid') !=
971 (None, None)) and not port_check(
972@@ -671,7 +490,7 @@
973 juju_log("disable_configsvr: port not defined.")
974 return(False)
975 try:
976- config_server_port = config_get('config_server_port')
977+ config_server_port = config('config_server_port')
978 pid = open('/var/run/mongodb/configsvr.pid').read()
979 os.kill(int(pid), signal.SIGTERM)
980 os.unlink('/var/run/mongodb/configsvr.pid')
981@@ -733,7 +552,7 @@
982
983
984 def mongos_status(wait_for=default_wait_for, max_tries=default_max_tries):
985- config_data = config_get()
986+ config_data = config()
987 current_try = 0
988 while (process_check_pidfile('/var/run/mongodb/mongos.pid') !=
989 (None, None)) and not port_check(
990@@ -825,17 +644,17 @@
991
992 def restart_mongod(wait_for=default_wait_for, max_tries=default_max_tries):
993 my_hostname = unit_get('public-address')
994- my_port = config_get('port')
995+ my_port = config('port')
996 current_try = 0
997
998- service('mongodb', 'stop')
999+ service('stop', 'mongodb')
1000 if os.path.exists('/var/lib/mongodb/mongod.lock'):
1001 os.remove('/var/lib/mongodb/mongod.lock')
1002
1003- if not service('mongodb', 'start'):
1004+ if not service('start', 'mongodb'):
1005 return False
1006
1007- while (service('mongodb', 'status') and
1008+ while (service('status', 'mongodb') and
1009 not port_check(my_hostname, my_port) and
1010 current_try < max_tries):
1011 juju_log(
1012@@ -845,14 +664,14 @@
1013 current_try += 1
1014
1015 return(
1016- (service('mongodb', 'status') == port_check(my_hostname, my_port))
1017+ (service('status', 'mongodb') == port_check(my_hostname, my_port))
1018 is True)
1019
1020
1021 def backup_cronjob(disable=False):
1022 """Generate the cronjob to backup with mongodbump."""
1023 juju_log('Setting up cronjob')
1024- config_data = config_get()
1025+ config_data = config()
1026 backupdir = config_data['backup_directory']
1027 bind_ip = config_data['bind_ip']
1028 cron_file = '/etc/cron.d/mongodb'
1029@@ -912,7 +731,7 @@
1030 def config_changed():
1031 juju_log("Entering config_changed")
1032 print "Entering config_changed"
1033- config_data = config_get()
1034+ config_data = config()
1035 print "config_data: ", config_data
1036 mongodb_config = open(default_mongodb_config).read()
1037
1038@@ -1054,7 +873,7 @@
1039 def stop_hook():
1040 juju_log("stop_hook")
1041 try:
1042- retVal = service('mongodb', 'stop')
1043+ retVal = service('stop', 'mongodb')
1044 os.remove('/var/lib/mongodb/mongod.lock')
1045 #FIXME Need to check if this is still needed
1046 except Exception, e:
1047@@ -1068,14 +887,14 @@
1048 def database_relation_joined():
1049 juju_log("database_relation_joined")
1050 my_hostname = unit_get('public-address')
1051- my_port = config_get('port')
1052- my_replset = config_get('replicaset')
1053+ my_port = config('port')
1054+ my_replset = config('replicaset')
1055 juju_log("my_hostname: %s" % my_hostname)
1056 juju_log("my_port: %s" % my_port)
1057 juju_log("my_replset: %s" % my_replset)
1058 return(relation_set(
1059 {
1060- 'hostname': my_hostname,
1061+ 'hostnme': my_hostname,
1062 'port': my_port,
1063 'replset': my_replset,
1064 'type': 'database',
1065@@ -1085,31 +904,35 @@
1066 def replica_set_relation_joined():
1067 juju_log("replica_set_relation_joined")
1068 my_hostname = unit_get('public-address')
1069- my_port = config_get('port')
1070- my_replset = config_get('replicaset')
1071+ my_port = config('port')
1072+ my_replset = config('replicaset')
1073 my_install_order = os.environ['JUJU_UNIT_NAME'].split('/')[1]
1074 juju_log("my_hostname: %s" % my_hostname)
1075 juju_log("my_port: %s" % my_port)
1076 juju_log("my_replset: %s" % my_replset)
1077 juju_log("my_install_order: %s" % my_install_order)
1078- return(enable_replset(my_replset) ==
1079- restart_mongod() ==
1080- relation_set(
1081- {
1082- 'hostname': my_hostname,
1083- 'port': my_port,
1084- 'replset': my_replset,
1085- 'install-order': my_install_order,
1086- 'type': 'replset',
1087- }))
1088+ enabled = enable_replset(my_replset)
1089+ restarted = restart_mongod()
1090+
1091+ relation_set(None, {
1092+ 'hostname': my_hostname,
1093+ 'port': my_port,
1094+ 'replset': my_replset,
1095+ 'install-order': my_install_order,
1096+ 'type': 'replset',
1097+ })
1098+
1099+ if enabled and restarted:
1100+ return True
1101+ return False
1102
1103
1104 def replica_set_relation_changed():
1105 juju_log("replica_set_relation_changed")
1106 my_hostname = unit_get('public-address')
1107- my_port = config_get('port')
1108+ my_port = config('port')
1109 my_install_order = os.environ['JUJU_UNIT_NAME'].split('/')[1]
1110- my_replicaset_master = config_get('replicaset_master')
1111+ my_replicaset_master = config('replicaset_master')
1112
1113 # If we are joining an existing replicaset cluster, just join and leave.
1114 if my_replicaset_master != "auto":
1115@@ -1121,11 +944,11 @@
1116 master_install_order = my_install_order
1117
1118 # Check the nodes in the relation to find the master
1119- for member in relation_list():
1120- juju_log("replica_set_relation_changed: member: %s" % member)
1121- hostname = relation_get('hostname', member)
1122- port = relation_get('port', member)
1123- install_order = relation_get('install-order', member)
1124+ for member in relations_of_type('replica-set'):
1125+ juju_log("replica_set_relation_changed: member: %s" % member['__unit__'])
1126+ hostname = relation_get('hostname', member['__unit__'])
1127+ port = relation_get('port', member['__unit__'])
1128+ install_order = relation_get('install-order', member['__unit__'])
1129 juju_log("replica_set_relation_changed: install_order: %s" % install_order)
1130 if install_order is None:
1131 juju_log("replica_set_relation_changed: install_order is None. relation is not ready")
1132@@ -1139,9 +962,9 @@
1133 init_replset("%s:%s" % (master_hostname, master_port))
1134
1135 # Add the rest of the nodes to the replset
1136- for member in relation_list():
1137- hostname = relation_get('hostname', member)
1138- port = relation_get('port', member)
1139+ for member in relations_of_type('replica-set'):
1140+ hostname = relation_get('hostname', member['__unit__'])
1141+ port = relation_get('port', member['__unit__'])
1142 if master_hostname != hostname:
1143 if hostname == my_hostname:
1144 subprocess.call(['mongo',
1145@@ -1156,13 +979,14 @@
1146 join_replset("%s:%s" % (master_hostname, master_port),
1147 "%s:%s" % (my_hostname, my_port))
1148
1149+ # should this always return true?
1150 return(True)
1151
1152
1153 def configsvr_relation_joined():
1154 juju_log("configsvr_relation_joined")
1155 my_hostname = unit_get('public-address')
1156- my_port = config_get('config_server_port')
1157+ my_port = config('config_server_port')
1158 my_install_order = os.environ['JUJU_UNIT_NAME'].split('/')[1]
1159 return(relation_set(
1160 {
1161@@ -1175,7 +999,7 @@
1162
1163 def configsvr_relation_changed():
1164 juju_log("configsvr_relation_changed")
1165- config_data = config_get()
1166+ config_data = config()
1167 my_port = config_data['config_server_port']
1168 disable_configsvr(my_port)
1169 retVal = enable_configsvr(config_data)
1170@@ -1186,7 +1010,7 @@
1171 def mongos_relation_joined():
1172 juju_log("mongos_relation_joined")
1173 my_hostname = unit_get('public-address')
1174- my_port = config_get('mongos_port')
1175+ my_port = config('mongos_port')
1176 my_install_order = os.environ['JUJU_UNIT_NAME'].split('/')[1]
1177 return(relation_set(
1178 {
1179@@ -1199,9 +1023,9 @@
1180
1181 def mongos_relation_changed():
1182 juju_log("mongos_relation_changed")
1183- config_data = config_get()
1184+ config_data = config()
1185 retVal = False
1186- for member in relation_list():
1187+ for member in relation_for_id():
1188 hostname = relation_get('hostname', member)
1189 port = relation_get('port', member)
1190 rel_type = relation_get('type', member)
1191@@ -1226,7 +1050,7 @@
1192 if mongos_ready():
1193 mongos_host = "%s:%s" % (
1194 unit_get('public-address'),
1195- config_get('mongos_port'))
1196+ config('mongos_port'))
1197 shard_command1 = "sh.addShard(\"%s:%s\")" % (hostname, port)
1198 retVal1 = mongo_client(mongos_host, shard_command1)
1199 replicaset = relation_get('replset', member)
1200@@ -1244,7 +1068,7 @@
1201
1202 def mongos_relation_broken():
1203 # config_servers = load_config_servers(default_mongos_list)
1204-# for member in relation_list():
1205+# for member in relation_for_id():
1206 # hostname = relation_get('hostname', member)
1207 # port = relation_get('port', member)
1208 # if '%s:%s' % (hostname, port) in config_servers:
1209@@ -1277,7 +1101,7 @@
1210 #
1211 #------------------------------
1212 def volume_get_volid_from_volume_map():
1213- config_data = config_get()
1214+ config_data = config()
1215 volume_map = {}
1216 try:
1217 volume_map = yaml.load(config_data['volume-map'].strip())
1218@@ -1316,7 +1140,7 @@
1219 # @returns volid
1220 # None config state is invalid - we should not serve
1221 def volume_get_volume_id():
1222- config_data = config_get()
1223+ config_data = config()
1224 ephemeral_storage = config_data['volume-ephemeral-storage']
1225 volid = volume_get_volid_from_volume_map()
1226 juju_unit_name = os.environ['JUJU_UNIT_NAME']
1227@@ -1367,7 +1191,7 @@
1228 # - manipulate /var/lib/mongodb/VERSION/CLUSTER symlink
1229 #------------------------------------------------------------------------------
1230 def config_changed_volume_apply():
1231- config_data = config_get()
1232+ config_data = config()
1233 data_directory_path = config_data["dbpath"]
1234 assert(data_directory_path)
1235 volid = volume_get_volume_id()
1236
1237=== modified file 'metadata.yaml'
1238--- metadata.yaml 2014-01-09 20:41:59 +0000
1239+++ metadata.yaml 2014-07-10 18:39:06 +0000
1240@@ -1,6 +1,9 @@
1241 name: mongodb
1242-maintainer: Juan Negron <juan.negron@canonical.com>
1243-summary: MongoDB (from humongous) is an open-source document database
1244+summary: An open-source document database, and the leading NoSQL database
1245+maintainers:
1246+ - Juan Negron <juan.negron@canonical.com>
1247+ - Marco Ceppi <marco@ceppi.net>
1248+ - Charles Butler <chuck@dasroot.net>
1249 description: |
1250 MongoDB is a high-performance, open source, schema-free document-
1251 oriented data store that's easy to deploy, manage and use. It's

Subscribers

People subscribed via source and target branches