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

Proposed by Charles Butler
Status: Merged
Merged at revision: 50
Proposed branch: lp:~lazypower/charms/trusty/mongodb/charmhelpers_merge
Merge into: lp:charms/mongodb
Diff against target: 1655 lines (+605/-490)
13 files modified
.bzrignore (+3/-0)
Makefile (+9/-1)
config.yaml (+1/-0)
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 (+150/-382)
hooks/install (+0/-5)
metadata.yaml (+5/-2)
tests/00-setup (+5/-5)
tests/200_relate_ceilometer.test (+43/-0)
To merge this branch: bzr merge lp:~lazypower/charms/trusty/mongodb/charmhelpers_merge
Reviewer Review Type Date Requested Status
Marco Ceppi (community) Approve
Cory Johns (community) Approve
Review via email: mp+228758@code.launchpad.net

Description of the change

Re-submission of charm-helpers migration. Adds validation test for Ceilometer. (note: when running tests, you will need to point teh cluster test at lp:~lazypower/charms/trusty/mongodb/charmhelpers_merge - as the store version will fail 100% of the time)

Please take a look

To post a comment you must log in.
55. By Charles Butler

adds missing import relation_ids

Revision history for this message
Cory Johns (johnsca) wrote :

Charles,

With the fixes as we discussed on IRC, I give this refactor my +1.

The tests all pass, with the caveat mentioned above (which will be resolved once it is merged), and the charm is overall cleaner. I particularly like the pattern for pulling in charm_helpers_sync.py and wonder if that could be leveraged into a pattern for pulling in charmhelpers at deploy time.

review: Approve
Revision history for this message
Marco Ceppi (marcoceppi) wrote :

lgtm

review: Approve
56. By Charles Butler

Fixes missing default on key, strips check around apt-add-repository

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-30 17:48:38 +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-30 17:48:38 +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=== modified file 'config.yaml'
35--- config.yaml 2014-06-18 11:13:54 +0000
36+++ config.yaml 2014-07-30 17:48:38 +0000
37@@ -211,6 +211,7 @@
38 option.
39 key:
40 type: string
41+ default:
42 description: >
43 Key ID to import to the apt keyring to support use with arbitary source
44 configuration from outside of Launchpad archives or PPA's.
45
46=== added file 'hooks/charmhelpers/core/fstab.py'
47--- hooks/charmhelpers/core/fstab.py 1970-01-01 00:00:00 +0000
48+++ hooks/charmhelpers/core/fstab.py 2014-07-30 17:48:38 +0000
49@@ -0,0 +1,116 @@
50+#!/usr/bin/env python
51+# -*- coding: utf-8 -*-
52+
53+__author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>'
54+
55+import os
56+
57+
58+class Fstab(file):
59+ """This class extends file in order to implement a file reader/writer
60+ for file `/etc/fstab`
61+ """
62+
63+ class Entry(object):
64+ """Entry class represents a non-comment line on the `/etc/fstab` file
65+ """
66+ def __init__(self, device, mountpoint, filesystem,
67+ options, d=0, p=0):
68+ self.device = device
69+ self.mountpoint = mountpoint
70+ self.filesystem = filesystem
71+
72+ if not options:
73+ options = "defaults"
74+
75+ self.options = options
76+ self.d = d
77+ self.p = p
78+
79+ def __eq__(self, o):
80+ return str(self) == str(o)
81+
82+ def __str__(self):
83+ return "{} {} {} {} {} {}".format(self.device,
84+ self.mountpoint,
85+ self.filesystem,
86+ self.options,
87+ self.d,
88+ self.p)
89+
90+ DEFAULT_PATH = os.path.join(os.path.sep, 'etc', 'fstab')
91+
92+ def __init__(self, path=None):
93+ if path:
94+ self._path = path
95+ else:
96+ self._path = self.DEFAULT_PATH
97+ file.__init__(self, self._path, 'r+')
98+
99+ def _hydrate_entry(self, line):
100+ # NOTE: use split with no arguments to split on any
101+ # whitespace including tabs
102+ return Fstab.Entry(*filter(
103+ lambda x: x not in ('', None),
104+ line.strip("\n").split()))
105+
106+ @property
107+ def entries(self):
108+ self.seek(0)
109+ for line in self.readlines():
110+ try:
111+ if not line.startswith("#"):
112+ yield self._hydrate_entry(line)
113+ except ValueError:
114+ pass
115+
116+ def get_entry_by_attr(self, attr, value):
117+ for entry in self.entries:
118+ e_attr = getattr(entry, attr)
119+ if e_attr == value:
120+ return entry
121+ return None
122+
123+ def add_entry(self, entry):
124+ if self.get_entry_by_attr('device', entry.device):
125+ return False
126+
127+ self.write(str(entry) + '\n')
128+ self.truncate()
129+ return entry
130+
131+ def remove_entry(self, entry):
132+ self.seek(0)
133+
134+ lines = self.readlines()
135+
136+ found = False
137+ for index, line in enumerate(lines):
138+ if not line.startswith("#"):
139+ if self._hydrate_entry(line) == entry:
140+ found = True
141+ break
142+
143+ if not found:
144+ return False
145+
146+ lines.remove(line)
147+
148+ self.seek(0)
149+ self.write(''.join(lines))
150+ self.truncate()
151+ return True
152+
153+ @classmethod
154+ def remove_by_mountpoint(cls, mountpoint, path=None):
155+ fstab = cls(path=path)
156+ entry = fstab.get_entry_by_attr('mountpoint', mountpoint)
157+ if entry:
158+ return fstab.remove_entry(entry)
159+ return False
160+
161+ @classmethod
162+ def add(cls, device, mountpoint, filesystem, options=None, path=None):
163+ return cls(path=path).add_entry(Fstab.Entry(device,
164+ mountpoint, filesystem,
165+ options=options))
166
167=== modified file 'hooks/charmhelpers/core/hookenv.py'
168--- hooks/charmhelpers/core/hookenv.py 2014-04-11 20:55:42 +0000
169+++ hooks/charmhelpers/core/hookenv.py 2014-07-30 17:48:38 +0000
170@@ -25,7 +25,7 @@
171 def cached(func):
172 """Cache return values for multiple executions of func + args
173
174- For example:
175+ For example::
176
177 @cached
178 def unit_get(attribute):
179@@ -155,6 +155,100 @@
180 return os.path.basename(sys.argv[0])
181
182
183+class Config(dict):
184+ """A Juju charm config dictionary that can write itself to
185+ disk (as json) and track which values have changed since
186+ the previous hook invocation.
187+
188+ Do not instantiate this object directly - instead call
189+ ``hookenv.config()``
190+
191+ Example usage::
192+
193+ >>> # inside a hook
194+ >>> from charmhelpers.core import hookenv
195+ >>> config = hookenv.config()
196+ >>> config['foo']
197+ 'bar'
198+ >>> config['mykey'] = 'myval'
199+ >>> config.save()
200+
201+
202+ >>> # user runs `juju set mycharm foo=baz`
203+ >>> # now we're inside subsequent config-changed hook
204+ >>> config = hookenv.config()
205+ >>> config['foo']
206+ 'baz'
207+ >>> # test to see if this val has changed since last hook
208+ >>> config.changed('foo')
209+ True
210+ >>> # what was the previous value?
211+ >>> config.previous('foo')
212+ 'bar'
213+ >>> # keys/values that we add are preserved across hooks
214+ >>> config['mykey']
215+ 'myval'
216+ >>> # don't forget to save at the end of hook!
217+ >>> config.save()
218+
219+ """
220+ CONFIG_FILE_NAME = '.juju-persistent-config'
221+
222+ def __init__(self, *args, **kw):
223+ super(Config, self).__init__(*args, **kw)
224+ self._prev_dict = None
225+ self.path = os.path.join(charm_dir(), Config.CONFIG_FILE_NAME)
226+ if os.path.exists(self.path):
227+ self.load_previous()
228+
229+ def load_previous(self, path=None):
230+ """Load previous copy of config from disk so that current values
231+ can be compared to previous values.
232+
233+ :param path:
234+
235+ File path from which to load the previous config. If `None`,
236+ config is loaded from the default location. If `path` is
237+ specified, subsequent `save()` calls will write to the same
238+ path.
239+
240+ """
241+ self.path = path or self.path
242+ with open(self.path) as f:
243+ self._prev_dict = json.load(f)
244+
245+ def changed(self, key):
246+ """Return true if the value for this key has changed since
247+ the last save.
248+
249+ """
250+ if self._prev_dict is None:
251+ return True
252+ return self.previous(key) != self.get(key)
253+
254+ def previous(self, key):
255+ """Return previous value for this key, or None if there
256+ is no "previous" value.
257+
258+ """
259+ if self._prev_dict:
260+ return self._prev_dict.get(key)
261+ return None
262+
263+ def save(self):
264+ """Save this config to disk.
265+
266+ Preserves items in _prev_dict that do not exist in self.
267+
268+ """
269+ if self._prev_dict:
270+ for k, v in self._prev_dict.iteritems():
271+ if k not in self:
272+ self[k] = v
273+ with open(self.path, 'w') as f:
274+ json.dump(self, f)
275+
276+
277 @cached
278 def config(scope=None):
279 """Juju charm configuration"""
280@@ -163,7 +257,10 @@
281 config_cmd_line.append(scope)
282 config_cmd_line.append('--format=json')
283 try:
284- return json.loads(subprocess.check_output(config_cmd_line))
285+ config_data = json.loads(subprocess.check_output(config_cmd_line))
286+ if scope is not None:
287+ return config_data
288+ return Config(config_data)
289 except ValueError:
290 return None
291
292@@ -348,18 +445,19 @@
293 class Hooks(object):
294 """A convenient handler for hook functions.
295
296- Example:
297+ Example::
298+
299 hooks = Hooks()
300
301 # register a hook, taking its name from the function name
302 @hooks.hook()
303 def install():
304- ...
305+ pass # your code here
306
307 # register a hook, providing a custom hook name
308 @hooks.hook("config-changed")
309 def config_changed():
310- ...
311+ pass # your code here
312
313 if __name__ == "__main__":
314 # execute a hook based on the name the program is called by
315
316=== modified file 'hooks/charmhelpers/core/host.py'
317--- hooks/charmhelpers/core/host.py 2014-04-11 20:55:42 +0000
318+++ hooks/charmhelpers/core/host.py 2014-07-30 17:48:38 +0000
319@@ -16,6 +16,7 @@
320 from collections import OrderedDict
321
322 from hookenv import log
323+from fstab import Fstab
324
325
326 def service_start(service_name):
327@@ -34,7 +35,8 @@
328
329
330 def service_reload(service_name, restart_on_failure=False):
331- """Reload a system service, optionally falling back to restart if reload fails"""
332+ """Reload a system service, optionally falling back to restart if
333+ reload fails"""
334 service_result = service('reload', service_name)
335 if not service_result and restart_on_failure:
336 service_result = service('restart', service_name)
337@@ -143,7 +145,19 @@
338 target.write(content)
339
340
341-def mount(device, mountpoint, options=None, persist=False):
342+def fstab_remove(mp):
343+ """Remove the given mountpoint entry from /etc/fstab
344+ """
345+ return Fstab.remove_by_mountpoint(mp)
346+
347+
348+def fstab_add(dev, mp, fs, options=None):
349+ """Adds the given device entry to the /etc/fstab file
350+ """
351+ return Fstab.add(dev, mp, fs, options=options)
352+
353+
354+def mount(device, mountpoint, options=None, persist=False, filesystem="ext3"):
355 """Mount a filesystem at a particular mountpoint"""
356 cmd_args = ['mount']
357 if options is not None:
358@@ -154,9 +168,9 @@
359 except subprocess.CalledProcessError, e:
360 log('Error mounting {} at {}\n{}'.format(device, mountpoint, e.output))
361 return False
362+
363 if persist:
364- # TODO: update fstab
365- pass
366+ return fstab_add(device, mountpoint, filesystem, options=options)
367 return True
368
369
370@@ -168,9 +182,9 @@
371 except subprocess.CalledProcessError, e:
372 log('Error unmounting {}\n{}'.format(mountpoint, e.output))
373 return False
374+
375 if persist:
376- # TODO: update fstab
377- pass
378+ return fstab_remove(mountpoint)
379 return True
380
381
382@@ -197,13 +211,13 @@
383 def restart_on_change(restart_map, stopstart=False):
384 """Restart services based on configuration files changing
385
386- This function is used a decorator, for example
387+ This function is used a decorator, for example::
388
389 @restart_on_change({
390 '/etc/ceph/ceph.conf': [ 'cinder-api', 'cinder-volume' ]
391 })
392 def ceph_client_changed():
393- ...
394+ pass # your code here
395
396 In this example, the cinder-api and cinder-volume services
397 would be restarted if /etc/ceph/ceph.conf is changed by the
398@@ -295,3 +309,19 @@
399 if 'link/ether' in words:
400 hwaddr = words[words.index('link/ether') + 1]
401 return hwaddr
402+
403+
404+def cmp_pkgrevno(package, revno, pkgcache=None):
405+ '''Compare supplied revno with the revno of the installed package
406+
407+ * 1 => Installed revno is greater than supplied arg
408+ * 0 => Installed revno is the same as supplied arg
409+ * -1 => Installed revno is less than supplied arg
410+
411+ '''
412+ import apt_pkg
413+ if not pkgcache:
414+ apt_pkg.init()
415+ pkgcache = apt_pkg.Cache()
416+ pkg = pkgcache[package]
417+ return apt_pkg.version_compare(pkg.current_ver.ver_str, revno)
418
419=== modified file 'hooks/charmhelpers/fetch/__init__.py'
420--- hooks/charmhelpers/fetch/__init__.py 2014-04-11 20:55:42 +0000
421+++ hooks/charmhelpers/fetch/__init__.py 2014-07-30 17:48:38 +0000
422@@ -1,4 +1,5 @@
423 import importlib
424+import time
425 from yaml import safe_load
426 from charmhelpers.core.host import (
427 lsb_release
428@@ -12,9 +13,9 @@
429 config,
430 log,
431 )
432-import apt_pkg
433 import os
434
435+
436 CLOUD_ARCHIVE = """# Ubuntu Cloud Archive
437 deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main
438 """
439@@ -54,12 +55,74 @@
440 'icehouse/proposed': 'precise-proposed/icehouse',
441 'precise-icehouse/proposed': 'precise-proposed/icehouse',
442 'precise-proposed/icehouse': 'precise-proposed/icehouse',
443+ # Juno
444+ 'juno': 'trusty-updates/juno',
445+ 'trusty-juno': 'trusty-updates/juno',
446+ 'trusty-juno/updates': 'trusty-updates/juno',
447+ 'trusty-updates/juno': 'trusty-updates/juno',
448+ 'juno/proposed': 'trusty-proposed/juno',
449+ 'juno/proposed': 'trusty-proposed/juno',
450+ 'trusty-juno/proposed': 'trusty-proposed/juno',
451+ 'trusty-proposed/juno': 'trusty-proposed/juno',
452 }
453
454+# The order of this list is very important. Handlers should be listed in from
455+# least- to most-specific URL matching.
456+FETCH_HANDLERS = (
457+ 'charmhelpers.fetch.archiveurl.ArchiveUrlFetchHandler',
458+ 'charmhelpers.fetch.bzrurl.BzrUrlFetchHandler',
459+)
460+
461+APT_NO_LOCK = 100 # The return code for "couldn't acquire lock" in APT.
462+APT_NO_LOCK_RETRY_DELAY = 10 # Wait 10 seconds between apt lock checks.
463+APT_NO_LOCK_RETRY_COUNT = 30 # Retry to acquire the lock X times.
464+
465+
466+class SourceConfigError(Exception):
467+ pass
468+
469+
470+class UnhandledSource(Exception):
471+ pass
472+
473+
474+class AptLockError(Exception):
475+ pass
476+
477+
478+class BaseFetchHandler(object):
479+
480+ """Base class for FetchHandler implementations in fetch plugins"""
481+
482+ def can_handle(self, source):
483+ """Returns True if the source can be handled. Otherwise returns
484+ a string explaining why it cannot"""
485+ return "Wrong source type"
486+
487+ def install(self, source):
488+ """Try to download and unpack the source. Return the path to the
489+ unpacked files or raise UnhandledSource."""
490+ raise UnhandledSource("Wrong source type {}".format(source))
491+
492+ def parse_url(self, url):
493+ return urlparse(url)
494+
495+ def base_url(self, url):
496+ """Return url without querystring or fragment"""
497+ parts = list(self.parse_url(url))
498+ parts[4:] = ['' for i in parts[4:]]
499+ return urlunparse(parts)
500+
501
502 def filter_installed_packages(packages):
503 """Returns a list of packages that require installation"""
504+ import apt_pkg
505 apt_pkg.init()
506+
507+ # Tell apt to build an in-memory cache to prevent race conditions (if
508+ # another process is already building the cache).
509+ apt_pkg.config.set("Dir::Cache::pkgcache", "")
510+
511 cache = apt_pkg.Cache()
512 _pkgs = []
513 for package in packages:
514@@ -87,14 +150,7 @@
515 cmd.extend(packages)
516 log("Installing {} with options: {}".format(packages,
517 options))
518- env = os.environ.copy()
519- if 'DEBIAN_FRONTEND' not in env:
520- env['DEBIAN_FRONTEND'] = 'noninteractive'
521-
522- if fatal:
523- subprocess.check_call(cmd, env=env)
524- else:
525- subprocess.call(cmd, env=env)
526+ _run_apt_command(cmd, fatal)
527
528
529 def apt_upgrade(options=None, fatal=False, dist=False):
530@@ -109,24 +165,13 @@
531 else:
532 cmd.append('upgrade')
533 log("Upgrading with options: {}".format(options))
534-
535- env = os.environ.copy()
536- if 'DEBIAN_FRONTEND' not in env:
537- env['DEBIAN_FRONTEND'] = 'noninteractive'
538-
539- if fatal:
540- subprocess.check_call(cmd, env=env)
541- else:
542- subprocess.call(cmd, env=env)
543+ _run_apt_command(cmd, fatal)
544
545
546 def apt_update(fatal=False):
547 """Update local apt cache"""
548 cmd = ['apt-get', 'update']
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_purge(packages, fatal=False):
557@@ -137,10 +182,7 @@
558 else:
559 cmd.extend(packages)
560 log("Purging {}".format(packages))
561- if fatal:
562- subprocess.check_call(cmd)
563- else:
564- subprocess.call(cmd)
565+ _run_apt_command(cmd, fatal)
566
567
568 def apt_hold(packages, fatal=False):
569@@ -151,6 +193,7 @@
570 else:
571 cmd.extend(packages)
572 log("Holding {}".format(packages))
573+
574 if fatal:
575 subprocess.check_call(cmd)
576 else:
577@@ -184,57 +227,50 @@
578 apt.write(PROPOSED_POCKET.format(release))
579 if key:
580 subprocess.check_call(['apt-key', 'adv', '--keyserver',
581- 'keyserver.ubuntu.com', '--recv',
582+ 'hkp://keyserver.ubuntu.com:80', '--recv',
583 key])
584
585
586-class SourceConfigError(Exception):
587- pass
588-
589-
590 def configure_sources(update=False,
591 sources_var='install_sources',
592 keys_var='install_keys'):
593 """
594- Configure multiple sources from charm configuration
595+ Configure multiple sources from charm configuration.
596+
597+ The lists are encoded as yaml fragments in the configuration.
598+ The frament needs to be included as a string.
599
600 Example config:
601- install_sources:
602+ install_sources: |
603 - "ppa:foo"
604 - "http://example.com/repo precise main"
605- install_keys:
606+ install_keys: |
607 - null
608 - "a1b2c3d4"
609
610 Note that 'null' (a.k.a. None) should not be quoted.
611 """
612- sources = safe_load(config(sources_var))
613- keys = config(keys_var)
614- if keys is not None:
615- keys = safe_load(keys)
616- if isinstance(sources, basestring) and (
617- keys is None or isinstance(keys, basestring)):
618- add_source(sources, keys)
619+ sources = safe_load((config(sources_var) or '').strip()) or []
620+ keys = safe_load((config(keys_var) or '').strip()) or None
621+
622+ if isinstance(sources, basestring):
623+ sources = [sources]
624+
625+ if keys is None:
626+ for source in sources:
627+ add_source(source, None)
628 else:
629- if not len(sources) == len(keys):
630- msg = 'Install sources and keys lists are different lengths'
631- raise SourceConfigError(msg)
632- for src_num in range(len(sources)):
633- add_source(sources[src_num], keys[src_num])
634+ if isinstance(keys, basestring):
635+ keys = [keys]
636+
637+ if len(sources) != len(keys):
638+ raise SourceConfigError(
639+ 'Install sources and keys lists are different lengths')
640+ for source, key in zip(sources, keys):
641+ add_source(source, key)
642 if update:
643 apt_update(fatal=True)
644
645-# The order of this list is very important. Handlers should be listed in from
646-# least- to most-specific URL matching.
647-FETCH_HANDLERS = (
648- 'charmhelpers.fetch.archiveurl.ArchiveUrlFetchHandler',
649- 'charmhelpers.fetch.bzrurl.BzrUrlFetchHandler',
650-)
651-
652-
653-class UnhandledSource(Exception):
654- pass
655-
656
657 def install_remote(source):
658 """
659@@ -265,30 +301,6 @@
660 return install_remote(source)
661
662
663-class BaseFetchHandler(object):
664-
665- """Base class for FetchHandler implementations in fetch plugins"""
666-
667- def can_handle(self, source):
668- """Returns True if the source can be handled. Otherwise returns
669- a string explaining why it cannot"""
670- return "Wrong source type"
671-
672- def install(self, source):
673- """Try to download and unpack the source. Return the path to the
674- unpacked files or raise UnhandledSource."""
675- raise UnhandledSource("Wrong source type {}".format(source))
676-
677- def parse_url(self, url):
678- return urlparse(url)
679-
680- def base_url(self, url):
681- """Return url without querystring or fragment"""
682- parts = list(self.parse_url(url))
683- parts[4:] = ['' for i in parts[4:]]
684- return urlunparse(parts)
685-
686-
687 def plugins(fetch_handlers=None):
688 if not fetch_handlers:
689 fetch_handlers = FETCH_HANDLERS
690@@ -306,3 +318,40 @@
691 log("FetchHandler {} not found, skipping plugin".format(
692 handler_name))
693 return plugin_list
694+
695+
696+def _run_apt_command(cmd, fatal=False):
697+ """
698+ Run an APT command, checking output and retrying if the fatal flag is set
699+ to True.
700+
701+ :param: cmd: str: The apt command to run.
702+ :param: fatal: bool: Whether the command's output should be checked and
703+ retried.
704+ """
705+ env = os.environ.copy()
706+
707+ if 'DEBIAN_FRONTEND' not in env:
708+ env['DEBIAN_FRONTEND'] = 'noninteractive'
709+
710+ if fatal:
711+ retry_count = 0
712+ result = None
713+
714+ # If the command is considered "fatal", we need to retry if the apt
715+ # lock was not acquired.
716+
717+ while result is None or result == APT_NO_LOCK:
718+ try:
719+ result = subprocess.check_call(cmd, env=env)
720+ except subprocess.CalledProcessError, e:
721+ retry_count = retry_count + 1
722+ if retry_count > APT_NO_LOCK_RETRY_COUNT:
723+ raise
724+ result = e.returncode
725+ log("Couldn't acquire DPKG lock. Will retry in {} seconds."
726+ "".format(APT_NO_LOCK_RETRY_DELAY))
727+ time.sleep(APT_NO_LOCK_RETRY_DELAY)
728+
729+ else:
730+ subprocess.call(cmd, env=env)
731
732=== modified file 'hooks/charmhelpers/fetch/bzrurl.py'
733--- hooks/charmhelpers/fetch/bzrurl.py 2014-04-11 20:55:42 +0000
734+++ hooks/charmhelpers/fetch/bzrurl.py 2014-07-30 17:48:38 +0000
735@@ -39,7 +39,8 @@
736 def install(self, source):
737 url_parts = self.parse_url(source)
738 branch_name = url_parts.path.strip("/").split("/")[-1]
739- dest_dir = os.path.join(os.environ.get('CHARM_DIR'), "fetched", branch_name)
740+ dest_dir = os.path.join(os.environ.get('CHARM_DIR'), "fetched",
741+ branch_name)
742 if not os.path.exists(dest_dir):
743 mkdir(dest_dir, perms=0755)
744 try:
745
746=== modified file 'hooks/hooks.py'
747--- hooks/hooks.py 2014-07-29 10:22:55 +0000
748+++ hooks/hooks.py 2014-07-30 17:48:38 +0000
749@@ -6,7 +6,6 @@
750 '''
751
752 import commands
753-import json
754 import os
755 import re
756 import signal
757@@ -15,7 +14,6 @@
758 import sys
759 import time
760 import yaml
761-import argparse
762
763 from os import chmod
764 from os import remove
765@@ -29,10 +27,27 @@
766 apt_update,
767 apt_install
768 )
769+
770 from charmhelpers.core.hookenv import (
771- config
772-)
773-
774+ config,
775+ unit_get,
776+ relation_get,
777+ relation_set,
778+ relations_of_type,
779+ relation_id,
780+ relation_ids,
781+ open_port,
782+ close_port,
783+ Hooks,
784+)
785+
786+from charmhelpers.core.hookenv import log as juju_log
787+
788+from charmhelpers.core.host import (
789+ service,
790+)
791+
792+hooks = Hooks()
793
794 ###############################################################################
795 # Global variables
796@@ -40,222 +55,14 @@
797 default_mongodb_config = "/etc/mongodb.conf"
798 default_mongodb_init_config = "/etc/init/mongodb.conf"
799 default_mongos_list = "/etc/mongos.list"
800-default_wait_for = 20
801-default_max_tries = 20
802+default_wait_for = 10
803+default_max_tries = 5
804
805 ###############################################################################
806 # Supporting functions
807 ###############################################################################
808
809
810-#------------------------------------------------------------------------------
811-# juju_log: calls juju-log and records the message defined by the message
812-# variable
813-#------------------------------------------------------------------------------
814-def juju_log(message=None):
815- return (subprocess.call(['juju-log', str(message)]) == 0)
816-
817-
818-#------------------------------------------------------------------------------
819-# service: Analogous to calling service on the command line to start/stop
820-# and get status of a service/daemon.
821-# Parameters:
822-# service_name: The name of the service to act on.
823-# service_action: The action (start, stop, status, etc.)
824-# Returns: True if the command was successfully executed or False on
825-# error.
826-#------------------------------------------------------------------------------
827-def service(service_name=None, service_action=None):
828- juju_log("service: %s, action: %s" % (service_name, service_action))
829- if service_name is not None and service_action is not None:
830- retVal = subprocess.call(
831- ["service", service_name, service_action]) == 0
832- else:
833- retVal = False
834- juju_log("service %s %s returns: %s" %
835- (service_name, service_action, retVal))
836- return(retVal)
837-
838-
839-#------------------------------------------------------------------------------
840-# unit_get: Convenience function wrapping the juju command unit-get
841-# Parameter:
842-# setting_name: The setting to get out of unit_get
843-# Returns: The requested information or None on error
844-#------------------------------------------------------------------------------
845-def unit_get(setting_name=None):
846- juju_log("unit_get: %s" % setting_name)
847- try:
848- cmd_line = ['unit-get', '--format=json']
849- if setting_name is not None:
850- cmd_line.append(setting_name)
851- unit_data = json.loads(subprocess.check_output(cmd_line))
852- except Exception, e:
853- subprocess.call(['juju-log', str(e)])
854- unit_data = None
855- finally:
856- juju_log("unit_get %s returns: %s" % (setting_name, unit_data))
857- return(unit_data)
858-
859-
860-#------------------------------------------------------------------------------
861-# config_get: Returns a dictionary containing all of the config information
862-# Optional parameter: scope
863-# scope: limits the scope of the returned configuration to the
864-# desired config item.
865-#------------------------------------------------------------------------------
866-def config_get(scope=None):
867- juju_log("config_get: %s" % scope)
868- try:
869- config_cmd_line = ['config-get']
870- if scope is not None:
871- config_cmd_line.append(scope)
872- config_cmd_line.append('--format=json')
873- config_data = json.loads(subprocess.check_output(config_cmd_line))
874- except Exception, e:
875- juju_log(str(e))
876- config_data = None
877- finally:
878- juju_log("config_get: %s returns: %s" % (scope, config_data))
879- return(config_data)
880-
881-
882-#------------------------------------------------------------------------------
883-# relation_get: Returns a dictionary containing the relation information
884-# Optional parameters: scope, relation_id
885-# scope: limits the scope of the returned data to the
886-# desired item.
887-# unit_name: limits the data ( and optionally the scope )
888-# to the specified unit
889-# relation_id: specify relation id for out of context usage.
890-#------------------------------------------------------------------------------
891-def relation_get(scope=None, unit_name=None, relation_id=None,
892- wait_for=default_wait_for, max_tries=default_max_tries):
893- juju_log("relation_get: scope: %s, unit_name: %s, relation_id: %s" %
894- (scope, unit_name, relation_id))
895- current_try = 0
896- try:
897- relation_cmd_line = ['relation-get', '--format=json']
898- if relation_id is not None:
899- relation_cmd_line.extend(('-r', relation_id))
900- if scope is not None:
901- relation_cmd_line.append(scope)
902- else:
903- relation_cmd_line.append('')
904- if unit_name is not None:
905- relation_cmd_line.append(unit_name)
906- relation_data = json.loads(subprocess.check_output(relation_cmd_line))
907-
908-# while relation_data is None and current_try < max_tries:
909-# time.sleep(wait_for)
910-# relation_data = json.loads(subprocess.check_output(relation_cmd_line))
911-# current_try += 1
912-
913- except Exception, e:
914- juju_log(str(e))
915- relation_data = None
916- finally:
917- juju_log("relation_get returns: %s" % relation_data)
918- return(relation_data)
919-
920-
921-#------------------------------------------------------------------------------
922-# relation_set: Convenience function wrapping the juju command relation-set
923-# Parameters:
924-# key_value_pairs: A dictionary containing the key/value pairs
925-# to be set.
926-# Optional Parameter:
927-# relation_id: The relation id to use
928-# Returns: True on success or False on failure
929-#------------------------------------------------------------------------------
930-def relation_set(key_value_pairs=None, relation_id=None):
931- juju_log("relation_set: kv: %s, relation_id: %s" %
932- (key_value_pairs, relation_id))
933- if key_value_pairs is None or not isinstance(key_value_pairs, dict):
934- juju_log("relation_set: Invalid key_value_pais.")
935- return(False)
936- try:
937- relation_cmd_line = ['relation-set', '--format=json']
938- if relation_id is not None:
939- relation_cmd_line.append('-r %s' % relation_id)
940- for (key, value) in key_value_pairs.items():
941- relation_cmd_line.append('%s=%s' % (key, value))
942- retVal = (subprocess.call(relation_cmd_line) == 0)
943- except Exception, e:
944- juju_log(str(e))
945- retVal = False
946- finally:
947- juju_log("relation_set returns: %s" % retVal)
948- return(retVal)
949-
950-
951-def relation_list(relation_id=None, wait_for=default_wait_for,
952- max_tries=default_max_tries):
953- juju_log("relation_list: relation_id: %s" % relation_id)
954- current_try = 0
955- try:
956- relation_cmd_line = ['relation-list', '--format=json']
957- if relation_id is not None:
958- relation_cmd_line.append('-r %s' % relation_id)
959- relation_data = json.loads(subprocess.check_output(relation_cmd_line))
960-
961-# while relation_data is None and current_try < max_tries:
962-# time.sleep(wait_for)
963-# relation_data = json.loads(subprocess.check_output(relation_cmd_line))
964-# current_try += 1
965-
966- except Exception, e:
967- juju_log(str(e))
968- relation_data = None
969- finally:
970- juju_log("relation_id %s returns: %s" % (relation_id, relation_data))
971- return(relation_data)
972-
973-
974-def relation_ids(relation_name=None):
975- juju_log("relation_ids: relation_name: %s" % relation_name)
976- try:
977- relation_cmd_line = ['relation-ids', '--format=json']
978- if relation_name is not None:
979- relation_cmd_line.append(relation_name)
980- relation_data = json.loads(subprocess.check_output(relation_cmd_line))
981- except Exception, e:
982- juju_log(str(e))
983- relation_data = None
984- finally:
985- juju_log("relation_ids %s returns: %s" % (relation_name, relation_data))
986- return(relation_data)
987-
988-#------------------------------------------------------------------------------
989-# open_port: Convenience function to open a port in juju to
990-# expose a service
991-#------------------------------------------------------------------------------
992-def open_port(port=None, protocol="TCP"):
993- juju_log("open_port: port: %d protocol: %s" % (int(port), protocol))
994- if port is None:
995- retVal = False
996- else:
997- retVal = subprocess.call(['open-port', "%d/%s" %
998- (int(port), protocol)]) == 0
999- juju_log("open_port %d/%s returns: %s" % (int(port), protocol, retVal))
1000- return(retVal)
1001-
1002-
1003-#------------------------------------------------------------------------------
1004-# close_port: Convenience function to close a port in juju to
1005-# unexpose a service
1006-#------------------------------------------------------------------------------
1007-def close_port(port=None, protocol="TCP"):
1008- juju_log("close_port: port: %d protocol: %s" % (int(port), protocol))
1009- if port is None:
1010- retVal = False
1011- else:
1012- retVal = subprocess.call(['close-port', "%d/%s" %
1013- (int(port), protocol)]) == 0
1014- juju_log("close_port %d/%s returns: %s" % (int(port), protocol, retVal))
1015- return(retVal)
1016-
1017-
1018 def port_check(host=None, port=None, protocol='TCP'):
1019 if host is None or port is None:
1020 juju_log("port_check: host and port must be defined.")
1021@@ -494,7 +301,7 @@
1022 config.append("")
1023
1024 # arbiter
1025- if config_data['arbiter'] != "disabled" and\
1026+ if config_data['arbiter'] != "disabled" and \
1027 config_data['arbiter'] != "enabled":
1028 config.append("arbiter = %s" % config_data['arbiter'])
1029 config.append("")
1030@@ -657,7 +464,7 @@
1031
1032
1033 def configsvr_status(wait_for=default_wait_for, max_tries=default_max_tries):
1034- config_data = config_get()
1035+ config_data = config()
1036 current_try = 0
1037 while (process_check_pidfile('/var/run/mongodb/configsvr.pid') !=
1038 (None, None)) and not port_check(
1039@@ -685,7 +492,7 @@
1040 juju_log("disable_configsvr: port not defined.")
1041 return(False)
1042 try:
1043- config_server_port = config_get('config_server_port')
1044+ config_server_port = config('config_server_port')
1045 pid = open('/var/run/mongodb/configsvr.pid').read()
1046 os.kill(int(pid), signal.SIGTERM)
1047 os.unlink('/var/run/mongodb/configsvr.pid')
1048@@ -747,7 +554,7 @@
1049
1050
1051 def mongos_status(wait_for=default_wait_for, max_tries=default_max_tries):
1052- config_data = config_get()
1053+ config_data = config()
1054 current_try = 0
1055 while (process_check_pidfile('/var/run/mongodb/mongos.pid') !=
1056 (None, None)) and not port_check(
1057@@ -839,17 +646,17 @@
1058
1059 def restart_mongod(wait_for=default_wait_for, max_tries=default_max_tries):
1060 my_hostname = unit_get('public-address')
1061- my_port = config_get('port')
1062+ my_port = config('port')
1063 current_try = 0
1064
1065- service('mongodb', 'stop')
1066+ service('stop', 'mongodb')
1067 if os.path.exists('/var/lib/mongodb/mongod.lock'):
1068 os.remove('/var/lib/mongodb/mongod.lock')
1069
1070- if not service('mongodb', 'start'):
1071+ if not service('start', 'mongodb'):
1072 return False
1073
1074- while (service('mongodb', 'status') and
1075+ while (service('status', 'mongodb') and
1076 not port_check(my_hostname, my_port) and
1077 current_try < max_tries):
1078 juju_log(
1079@@ -859,14 +666,14 @@
1080 current_try += 1
1081
1082 return(
1083- (service('mongodb', 'status') == port_check(my_hostname, my_port))
1084+ (service('status', 'mongodb') == port_check(my_hostname, my_port))
1085 is True)
1086
1087
1088 def backup_cronjob(disable=False):
1089 """Generate the cronjob to backup with mongodbump."""
1090 juju_log('Setting up cronjob')
1091- config_data = config_get()
1092+ config_data = config()
1093 backupdir = config_data['backup_directory']
1094 bind_ip = config_data['bind_ip']
1095 cron_file = '/etc/cron.d/mongodb'
1096@@ -915,18 +722,19 @@
1097 ###############################################################################
1098 # Hook functions
1099 ###############################################################################
1100+@hooks.hook('install')
1101 def install_hook():
1102 juju_log("Installing mongodb")
1103 add_source(config('source'), config('key'))
1104 apt_update(fatal=True)
1105 apt_install(packages=['mongodb', 'python-yaml'], fatal=True)
1106- return True
1107-
1108-
1109+
1110+
1111+@hooks.hook('config-changed')
1112 def config_changed():
1113 juju_log("Entering config_changed")
1114 print "Entering config_changed"
1115- config_data = config_get()
1116+ config_data = config()
1117 print "config_data: ", config_data
1118 mongodb_config = open(default_mongodb_config).read()
1119
1120@@ -1048,7 +856,7 @@
1121 juju_log("config_changed: Exceptions: %s" % str(e))
1122
1123 if mongos_pid is not None:
1124- mongos_port = re.search('--port (\w+)', mongos_cmd_line).group(2)
1125+ mongos_port = re.search('--port (\w+)', mongos_cmd_line).group(1)
1126 disable_mongos(mongos_port)
1127 enable_mongos(config_data['mongos_port'])
1128 else:
1129@@ -1058,6 +866,7 @@
1130 return(True)
1131
1132
1133+@hooks.hook('start')
1134 def start_hook():
1135 juju_log("start_hook")
1136 retVal = restart_mongod()
1137@@ -1065,10 +874,11 @@
1138 return(retVal)
1139
1140
1141+@hooks.hook('stop')
1142 def stop_hook():
1143 juju_log("stop_hook")
1144 try:
1145- retVal = service('mongodb', 'stop')
1146+ retVal = service('stop', 'mongodb')
1147 os.remove('/var/lib/mongodb/mongod.lock')
1148 #FIXME Need to check if this is still needed
1149 except Exception, e:
1150@@ -1079,15 +889,16 @@
1151 return(retVal)
1152
1153
1154+@hooks.hook('database-relation-joined')
1155 def database_relation_joined():
1156 juju_log("database_relation_joined")
1157 my_hostname = unit_get('public-address')
1158- my_port = config_get('port')
1159- my_replset = config_get('replicaset')
1160+ my_port = config('port')
1161+ my_replset = config('replicaset')
1162 juju_log("my_hostname: %s" % my_hostname)
1163 juju_log("my_port: %s" % my_port)
1164 juju_log("my_replset: %s" % my_replset)
1165- return(relation_set(
1166+ return(relation_set(relation_id(),
1167 {
1168 'hostname': my_hostname,
1169 'port': my_port,
1170@@ -1096,34 +907,36 @@
1171 }))
1172
1173
1174+@hooks.hook('replicaset-relation-joined')
1175 def replica_set_relation_joined():
1176 juju_log("replica_set_relation_joined")
1177 my_hostname = unit_get('public-address')
1178- my_port = config_get('port')
1179- my_replset = config_get('replicaset')
1180+ my_port = config('port')
1181+ my_replset = config('replicaset')
1182 my_install_order = os.environ['JUJU_UNIT_NAME'].split('/')[1]
1183 juju_log("my_hostname: %s" % my_hostname)
1184 juju_log("my_port: %s" % my_port)
1185 juju_log("my_replset: %s" % my_replset)
1186 juju_log("my_install_order: %s" % my_install_order)
1187- return(enable_replset(my_replset) ==
1188- restart_mongod() ==
1189- relation_set(
1190- {
1191- 'hostname': my_hostname,
1192- 'port': my_port,
1193- 'replset': my_replset,
1194- 'install-order': my_install_order,
1195- 'type': 'replset',
1196- }))
1197-
1198-
1199+ enable_replset(my_replset)
1200+ restart_mongod()
1201+
1202+ relation_set(relation_id(), {
1203+ 'hostname': my_hostname,
1204+ 'port': my_port,
1205+ 'replset': my_replset,
1206+ 'install-order': my_install_order,
1207+ 'type': 'replset',
1208+ })
1209+
1210+
1211+@hooks.hook('replicaset-relation-changed')
1212 def replica_set_relation_changed():
1213 juju_log("replica_set_relation_changed")
1214 my_hostname = unit_get('public-address')
1215- my_port = config_get('port')
1216+ my_port = config('port')
1217 my_install_order = os.environ['JUJU_UNIT_NAME'].split('/')[1]
1218- my_replicaset_master = config_get('replicaset_master')
1219+ my_replicaset_master = config('replicaset_master')
1220
1221 # If we are joining an existing replicaset cluster, just join and leave.
1222 if my_replicaset_master != "auto":
1223@@ -1135,44 +948,44 @@
1224 master_install_order = my_install_order
1225
1226 # Check the nodes in the relation to find the master
1227- for member in relation_list():
1228+ for member in relations_of_type('replica-set'):
1229+ member = member['__unit__']
1230 juju_log("replica_set_relation_changed: member: %s" % member)
1231 hostname = relation_get('hostname', member)
1232 port = relation_get('port', member)
1233- install_order = relation_get('install-order', member)
1234- juju_log("replica_set_relation_changed: install_order: %s" % install_order)
1235- if install_order is None:
1236- juju_log("replica_set_relation_changed: install_order is None. relation is not ready")
1237+ inst_ordr = relation_get('install-order', member)
1238+ juju_log("replica_set_relation_changed: install_order: %s" % inst_ordr)
1239+ if inst_ordr is None:
1240+ juju_log("replica_set_relation_changed: install_order is None."
1241+ " relation is not ready")
1242 break
1243- if int(install_order) < int(master_install_order):
1244+ if int(inst_ordr) < int(master_install_order):
1245 master_hostname = hostname
1246 master_port = port
1247- master_install_order = install_order
1248+ master_install_order = inst_ordr
1249
1250 # Initiate the replset
1251 init_replset("%s:%s" % (master_hostname, master_port))
1252
1253 # Add the rest of the nodes to the replset
1254- for member in relation_list():
1255- hostname = relation_get('hostname', member)
1256- port = relation_get('port', member)
1257+ for member in relations_of_type('replica-set'):
1258+ hostname = relation_get('hostname', member['__unit__'])
1259+ port = relation_get('port', member['__unit__'])
1260 if master_hostname != hostname:
1261 if hostname == my_hostname:
1262- subprocess.call(['mongo',
1263- '--eval',
1264- "rs.add(\"%s\")" % hostname])
1265+ subprocess.call(['mongo', '--eval',
1266+ "rs.add(\"%s\")" % hostname])
1267 else:
1268 join_replset("%s:%s" % (master_hostname, master_port),
1269- "%s:%s" % (hostname, port))
1270+ "%s:%s" % (hostname, port))
1271
1272 # Add this node to the replset ( if needed )
1273 if master_hostname != my_hostname:
1274 join_replset("%s:%s" % (master_hostname, master_port),
1275- "%s:%s" % (my_hostname, my_port))
1276-
1277- return(True)
1278-
1279-
1280+ "%s:%s" % (my_hostname, my_port))
1281+
1282+
1283+@hooks.hook('data-relation-joined')
1284 def data_relation_joined():
1285 juju_log("data_relation_joined")
1286
1287@@ -1182,6 +995,7 @@
1288 }))
1289
1290
1291+@hooks.hook('data-relation-changed')
1292 def data_relation_changed():
1293 juju_log("data_relation_changed")
1294
1295@@ -1189,60 +1003,63 @@
1296 juju_log("mountpoint from storage subordinate not ready, let's wait")
1297 return(True)
1298
1299- return(config_changed())
1300-
1301-
1302+ config_changed()
1303+
1304+
1305+@hooks.hook('data-relation-departed')
1306 def data_relation_departed():
1307 juju_log("data_relation_departed")
1308 return(config_changed())
1309
1310-
1311+@hooks.hook('configsvr-relation-joined')
1312 def configsvr_relation_joined():
1313 juju_log("configsvr_relation_joined")
1314 my_hostname = unit_get('public-address')
1315- my_port = config_get('config_server_port')
1316+ my_port = config('config_server_port')
1317 my_install_order = os.environ['JUJU_UNIT_NAME'].split('/')[1]
1318- return(relation_set(
1319- {
1320- 'hostname': my_hostname,
1321- 'port': my_port,
1322- 'install-order': my_install_order,
1323- 'type': 'configsvr',
1324- }))
1325-
1326-
1327+ relation_set(relation_id(),
1328+ {
1329+ 'hostname': my_hostname,
1330+ 'port': my_port,
1331+ 'install-order': my_install_order,
1332+ 'type': 'configsvr',
1333+ })
1334+
1335+
1336+@hooks.hook('configsvr-relation-changed')
1337 def configsvr_relation_changed():
1338 juju_log("configsvr_relation_changed")
1339- config_data = config_get()
1340+ config_data = config()
1341 my_port = config_data['config_server_port']
1342 disable_configsvr(my_port)
1343- retVal = enable_configsvr(config_data)
1344- juju_log("configsvr_relation_changed returns: %s" % retVal)
1345- return(retVal)
1346-
1347-
1348+
1349+
1350+@hooks.hook('mongos-cfg-relation-joined')
1351+@hooks.hook('mongos-relation-joined')
1352 def mongos_relation_joined():
1353 juju_log("mongos_relation_joined")
1354 my_hostname = unit_get('public-address')
1355- my_port = config_get('mongos_port')
1356+ my_port = config('mongos_port')
1357 my_install_order = os.environ['JUJU_UNIT_NAME'].split('/')[1]
1358- return(relation_set(
1359- {
1360- 'hostname': my_hostname,
1361- 'port': my_port,
1362- 'install-order': my_install_order,
1363- 'type': 'mongos'
1364- }))
1365-
1366-
1367+ relation_set(relation_id(),
1368+ {
1369+ 'hostname': my_hostname,
1370+ 'port': my_port,
1371+ 'install-order': my_install_order,
1372+ 'type': 'mongos'
1373+ })
1374+
1375+
1376+@hooks.hook('mongos-cfg-relation-changed')
1377+@hooks.hook('mongos-relation-changed')
1378 def mongos_relation_changed():
1379 juju_log("mongos_relation_changed")
1380- config_data = config_get()
1381+ config_data = config()
1382 retVal = False
1383- for member in relation_list():
1384- hostname = relation_get('hostname', member)
1385- port = relation_get('port', member)
1386- rel_type = relation_get('type', member)
1387+ for member in relations_of_type('mongos-cfg'):
1388+ hostname = relation_get('hostname', member['__unit__'])
1389+ port = relation_get('port', member['__unit__'])
1390+ rel_type = relation_get('type', member['__unit__'])
1391 if hostname is None or port is None or rel_type is None:
1392 juju_log("mongos_relation_changed: relation data not ready.")
1393 break
1394@@ -1264,34 +1081,33 @@
1395 if mongos_ready():
1396 mongos_host = "%s:%s" % (
1397 unit_get('public-address'),
1398- config_get('mongos_port'))
1399+ config('mongos_port'))
1400 shard_command1 = "sh.addShard(\"%s:%s\")" % (hostname, port)
1401- retVal1 = mongo_client(mongos_host, shard_command1)
1402+ mongo_client(mongos_host, shard_command1)
1403 replicaset = relation_get('replset', member)
1404- shard_command2 = "sh.addShard(\"%s/%s:%s\")" % \
1405+ shard_command2 = "sh.addShard(\"%s/%s:%s\")" % \
1406 (replicaset, hostname, port)
1407- retVal2 = mongo_client(mongos_host, shard_command2)
1408- retVal = retVal1 is True and retVal2 is True
1409- else:
1410- juju_log("Not enough config server for mongos yet.")
1411- retVal = True
1412+ mongo_client(mongos_host, shard_command2)
1413+
1414+
1415 else:
1416 juju_log("mongos_relation_change: undefined rel_type: %s" %
1417- rel_type)
1418+ rel_type)
1419 return(False)
1420 juju_log("mongos_relation_changed returns: %s" % retVal)
1421- return(retVal)
1422-
1423-
1424+
1425+
1426+
1427+@hooks.hook('mongos-relation-broken')
1428 def mongos_relation_broken():
1429-# config_servers = load_config_servers(default_mongos_list)
1430-# for member in relation_list():
1431-# hostname = relation_get('hostname', member)
1432-# port = relation_get('port', member)
1433-# if '%s:%s' % (hostname, port) in config_servers:
1434-# config_servers.remove('%s:%s' % (hostname, port))
1435-# return(update_file(default_mongos_list, '\n'.join(config_servers)))
1436- return(True)
1437+ config_servers = load_config_servers(default_mongos_list)
1438+ for member in relations_of_type('mongos'):
1439+ hostname = relation_get('hostname', member)
1440+ port = relation_get('port', member)
1441+ if '%s:%s' % (hostname, port) in config_servers:
1442+ config_servers.remove('%s:%s' % (hostname, port))
1443+
1444+ update_file(default_mongos_list, '\n'.join(config_servers))
1445
1446
1447 def run(command, exit_on_error=True):
1448@@ -1318,7 +1134,7 @@
1449 #
1450 #------------------------------
1451 def volume_get_volid_from_volume_map():
1452- config_data = config_get()
1453+ config_data = config()
1454 volume_map = {}
1455 try:
1456 volume_map = yaml.load(config_data['volume-map'].strip())
1457@@ -1379,19 +1195,21 @@
1458 # None config state is invalid - we should not serve
1459 def volume_get_volume_id():
1460
1461+ config_data = config()
1462+
1463+
1464
1465 volid = volume_get_id_for_storage_subordinate()
1466 if volid:
1467 return volid
1468
1469- config_data = config_get()
1470 ephemeral_storage = config_data['volume-ephemeral-storage']
1471 volid = volume_get_volid_from_volume_map()
1472 juju_unit_name = os.environ['JUJU_UNIT_NAME']
1473 if ephemeral_storage in [True, 'yes', 'Yes', 'true', 'True']:
1474 if volid:
1475 juju_log(
1476- "volume-ephemeral-storage is True, but " +
1477+ "volume-ephemeral-storage is True, but"
1478 "volume-map[{!r}] -> {}".format(juju_unit_name, volid))
1479 return None
1480 else:
1481@@ -1424,6 +1242,7 @@
1482 return None
1483 return output
1484
1485+
1486 #------------------------------------------------------------------------------
1487 # Core logic for permanent storage changes:
1488 # NOTE the only 2 "True" return points:
1489@@ -1435,7 +1254,7 @@
1490 # - manipulate /var/lib/mongodb/VERSION/CLUSTER symlink
1491 #------------------------------------------------------------------------------
1492 def config_changed_volume_apply():
1493- config_data = config_get()
1494+ config_data = config()
1495 data_directory_path = config_data["dbpath"]
1496 assert(data_directory_path)
1497 volid = volume_get_volume_id()
1498@@ -1548,57 +1367,6 @@
1499 ###############################################################################
1500 # Main section
1501 ###############################################################################
1502-if __name__ == '__main__':
1503- parser = argparse.ArgumentParser()
1504- parser.add_argument('-H', '--hook_name', dest='hook_name',
1505- help='hook to call')
1506- args = parser.parse_args()
1507- if args.hook_name is not None:
1508- hook_name = args.hook_name
1509- else:
1510- hook_name = os.path.basename(sys.argv[0])
1511-
1512- if hook_name == "install":
1513- retVal = install_hook()
1514- elif hook_name == "config-changed":
1515- retVal = config_changed()
1516- elif hook_name == "start":
1517- retVal = start_hook()
1518- elif hook_name == "stop":
1519- retVal = stop_hook()
1520- elif hook_name == "database-relation-joined":
1521- retVal = database_relation_joined()
1522- elif hook_name == "replica-set-relation-joined":
1523- retVal = replica_set_relation_joined()
1524- elif hook_name == "replica-set-relation-changed":
1525- retVal = replica_set_relation_changed()
1526- elif hook_name == "configsvr-relation-joined":
1527- retVal = configsvr_relation_joined()
1528- elif hook_name == "configsvr-relation-changed":
1529- retVal = configsvr_relation_changed()
1530- elif hook_name == "mongos-cfg-relation-joined":
1531- retVal = mongos_relation_joined()
1532- elif hook_name == "mongos-cfg-relation-changed":
1533- retVal = mongos_relation_changed()
1534- elif hook_name == "mongos-cfg-relation-broken":
1535- retVal = mongos_relation_broken()
1536- elif hook_name == "mongos-relation-joined":
1537- retVal = mongos_relation_joined()
1538- elif hook_name == "mongos-relation-changed":
1539- retVal = mongos_relation_changed()
1540- elif hook_name == "mongos-relation-broken":
1541- retVal = mongos_relation_broken()
1542- elif hook_name == "data-relation-joined":
1543- retVal = data_relation_joined()
1544- elif hook_name == "data-relation-changed":
1545- retVal = data_relation_changed()
1546- elif hook_name == "data-relation-departed":
1547- retVal = data_relation_departed()
1548- else:
1549- print "Unknown hook"
1550- retVal = False
1551-
1552- if retVal is True:
1553- sys.exit(0)
1554- else:
1555- sys.exit(1)
1556+if __name__ == "__main__":
1557+ # execute a hook based on the name the program is called by
1558+ hooks.execute(sys.argv)
1559\ No newline at end of file
1560
1561=== modified file 'hooks/install'
1562--- hooks/install 2013-11-25 19:48:00 +0000
1563+++ hooks/install 1970-01-01 00:00:00 +0000
1564@@ -1,5 +0,0 @@
1565-#!/bin/bash
1566-
1567-sudo apt-get install "python-yaml"
1568-
1569-hooks/hooks.py -H install
1570
1571=== target is u'hooks.py'
1572=== modified file 'metadata.yaml'
1573--- metadata.yaml 2014-06-18 11:13:54 +0000
1574+++ metadata.yaml 2014-07-30 17:48:38 +0000
1575@@ -1,6 +1,9 @@
1576 name: mongodb
1577-maintainer: Juan Negron <juan.negron@canonical.com>
1578-summary: MongoDB (from humongous) is an open-source document database
1579+summary: An open-source document database, and the leading NoSQL database
1580+maintainers:
1581+ - Juan Negron <juan.negron@canonical.com>
1582+ - Marco Ceppi <marco@ceppi.net>
1583+ - Charles Butler <chuck@dasroot.net>
1584 description: |
1585 MongoDB is a high-performance, open source, schema-free document-
1586 oriented data store that's easy to deploy, manage and use. It's
1587
1588=== modified file 'tests/00-setup'
1589--- tests/00-setup 2014-02-25 21:37:07 +0000
1590+++ tests/00-setup 2014-07-30 17:48:38 +0000
1591@@ -1,11 +1,11 @@
1592 #!/bin/bash
1593
1594+set -e
1595+
1596 sudo apt-get install python-setuptools -y
1597-
1598-if [ -f '/etc/apt.d/sources.list.d/juju-stable-precise.list' ]; then
1599- sudo add-apt-repository ppa:juju/stable -y
1600-fi
1601+sudo add-apt-repository ppa:juju/stable -y
1602
1603 sudo apt-get update
1604
1605-sudo apt-get install amulet python3 python3-requests python3-pymongo juju-core charm-tools python-mock -y
1606+
1607+sudo apt-get install amulet python3 python3-requests python3-pymongo juju-core charm-tools python-mock python-pymongo -y
1608
1609=== added file 'tests/200_relate_ceilometer.test'
1610--- tests/200_relate_ceilometer.test 1970-01-01 00:00:00 +0000
1611+++ tests/200_relate_ceilometer.test 2014-07-30 17:48:38 +0000
1612@@ -0,0 +1,43 @@
1613+#!/usr/bin/env python3
1614+
1615+import amulet
1616+import pdb
1617+
1618+class TestDeploy(object):
1619+
1620+ def __init__(self, time=2500):
1621+ # Attempt to load the deployment topology from a bundle.
1622+ self.deploy = amulet.Deployment(series="trusty")
1623+
1624+ # If something errored out, attempt to continue by
1625+ # manually specifying a standalone deployment
1626+ self.deploy.add('mongodb')
1627+ self.deploy.add('ceilometer', 'cs:trusty/ceilometer')
1628+ # send blank configs to finalize the objects in the deployment map
1629+ self.deploy.configure('mongodb', {})
1630+ self.deploy.configure('ceilometer', {})
1631+
1632+ self.deploy.relate('mongodb:database', 'ceilometer:shared-db')
1633+
1634+ try:
1635+ self.deploy.setup(time)
1636+ self.deploy.sentry.wait(time)
1637+ except:
1638+ amulet.raise_status(amulet.FAIL, msg="Environment standup timeout")
1639+ # sentry = self.deploy.sentry
1640+
1641+ def run(self):
1642+ for test in dir(self):
1643+ if test.startswith('test_'):
1644+ getattr(self, test)()
1645+
1646+ def test_mongo_relation(self):
1647+ unit = self.deploy.sentry.unit['ceilometer/0']
1648+ mongo = self.deploy.sentry.unit['mongodb/0'].info['public-address']
1649+ cont = unit.file_contents('/etc/ceilometer/ceilometer.conf')
1650+ if mongo not in cont:
1651+ amulet.raise_status(amulet.FAIL, "Unable to verify ceilometer cfg")
1652+
1653+if __name__ == '__main__':
1654+ runner = TestDeploy()
1655+ runner.run()

Subscribers

People subscribed via source and target branches