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
=== added file '.bzrignore'
--- .bzrignore 1970-01-01 00:00:00 +0000
+++ .bzrignore 2014-07-30 17:48:38 +0000
@@ -0,0 +1,3 @@
1.git
2bin/*
3scripts/charm-helpers-sync.py
04
=== modified file 'Makefile'
--- Makefile 2014-04-11 20:55:42 +0000
+++ Makefile 2014-07-30 17:48:38 +0000
@@ -13,9 +13,17 @@
13# You should have received a copy of the GNU Affero General Public License13# 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/>.14# along with this program. If not, see <http://www.gnu.org/licenses/>.
1515
16PYTHON := /usr/bin/env python
1617
17unittest:18unittest:
18 tests/10-unit.test19 tests/10-unit.test
1920
20sync:21sync:
21 @charm-helper-sync -c charm-helpers-sync.yaml22 @mkdir -p bin
23 @bzr cat lp:charm-helpers/tools/charm_helpers_sync/charm_helpers_sync.py > bin/charm_helpers_sync.py
24 @$(PYTHON) bin/charm_helpers_sync.py -c charm-helpers-sync.yaml
25
26clean:
27 @find . -name \*.pyc -delete
28 @find . -name '*.bak' -delete
29
2230
=== added file '__init__.py'
=== added directory 'bin'
=== modified file 'config.yaml'
--- config.yaml 2014-06-18 11:13:54 +0000
+++ config.yaml 2014-07-30 17:48:38 +0000
@@ -211,6 +211,7 @@
211 option.211 option.
212 key:212 key:
213 type: string213 type: string
214 default:
214 description: >215 description: >
215 Key ID to import to the apt keyring to support use with arbitary source216 Key ID to import to the apt keyring to support use with arbitary source
216 configuration from outside of Launchpad archives or PPA's.217 configuration from outside of Launchpad archives or PPA's.
217218
=== added file 'hooks/charmhelpers/core/fstab.py'
--- hooks/charmhelpers/core/fstab.py 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/core/fstab.py 2014-07-30 17:48:38 +0000
@@ -0,0 +1,116 @@
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4__author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>'
5
6import os
7
8
9class Fstab(file):
10 """This class extends file in order to implement a file reader/writer
11 for file `/etc/fstab`
12 """
13
14 class Entry(object):
15 """Entry class represents a non-comment line on the `/etc/fstab` file
16 """
17 def __init__(self, device, mountpoint, filesystem,
18 options, d=0, p=0):
19 self.device = device
20 self.mountpoint = mountpoint
21 self.filesystem = filesystem
22
23 if not options:
24 options = "defaults"
25
26 self.options = options
27 self.d = d
28 self.p = p
29
30 def __eq__(self, o):
31 return str(self) == str(o)
32
33 def __str__(self):
34 return "{} {} {} {} {} {}".format(self.device,
35 self.mountpoint,
36 self.filesystem,
37 self.options,
38 self.d,
39 self.p)
40
41 DEFAULT_PATH = os.path.join(os.path.sep, 'etc', 'fstab')
42
43 def __init__(self, path=None):
44 if path:
45 self._path = path
46 else:
47 self._path = self.DEFAULT_PATH
48 file.__init__(self, self._path, 'r+')
49
50 def _hydrate_entry(self, line):
51 # NOTE: use split with no arguments to split on any
52 # whitespace including tabs
53 return Fstab.Entry(*filter(
54 lambda x: x not in ('', None),
55 line.strip("\n").split()))
56
57 @property
58 def entries(self):
59 self.seek(0)
60 for line in self.readlines():
61 try:
62 if not line.startswith("#"):
63 yield self._hydrate_entry(line)
64 except ValueError:
65 pass
66
67 def get_entry_by_attr(self, attr, value):
68 for entry in self.entries:
69 e_attr = getattr(entry, attr)
70 if e_attr == value:
71 return entry
72 return None
73
74 def add_entry(self, entry):
75 if self.get_entry_by_attr('device', entry.device):
76 return False
77
78 self.write(str(entry) + '\n')
79 self.truncate()
80 return entry
81
82 def remove_entry(self, entry):
83 self.seek(0)
84
85 lines = self.readlines()
86
87 found = False
88 for index, line in enumerate(lines):
89 if not line.startswith("#"):
90 if self._hydrate_entry(line) == entry:
91 found = True
92 break
93
94 if not found:
95 return False
96
97 lines.remove(line)
98
99 self.seek(0)
100 self.write(''.join(lines))
101 self.truncate()
102 return True
103
104 @classmethod
105 def remove_by_mountpoint(cls, mountpoint, path=None):
106 fstab = cls(path=path)
107 entry = fstab.get_entry_by_attr('mountpoint', mountpoint)
108 if entry:
109 return fstab.remove_entry(entry)
110 return False
111
112 @classmethod
113 def add(cls, device, mountpoint, filesystem, options=None, path=None):
114 return cls(path=path).add_entry(Fstab.Entry(device,
115 mountpoint, filesystem,
116 options=options))
0117
=== modified file 'hooks/charmhelpers/core/hookenv.py'
--- hooks/charmhelpers/core/hookenv.py 2014-04-11 20:55:42 +0000
+++ hooks/charmhelpers/core/hookenv.py 2014-07-30 17:48:38 +0000
@@ -25,7 +25,7 @@
25def cached(func):25def cached(func):
26 """Cache return values for multiple executions of func + args26 """Cache return values for multiple executions of func + args
2727
28 For example:28 For example::
2929
30 @cached30 @cached
31 def unit_get(attribute):31 def unit_get(attribute):
@@ -155,6 +155,100 @@
155 return os.path.basename(sys.argv[0])155 return os.path.basename(sys.argv[0])
156156
157157
158class Config(dict):
159 """A Juju charm config dictionary that can write itself to
160 disk (as json) and track which values have changed since
161 the previous hook invocation.
162
163 Do not instantiate this object directly - instead call
164 ``hookenv.config()``
165
166 Example usage::
167
168 >>> # inside a hook
169 >>> from charmhelpers.core import hookenv
170 >>> config = hookenv.config()
171 >>> config['foo']
172 'bar'
173 >>> config['mykey'] = 'myval'
174 >>> config.save()
175
176
177 >>> # user runs `juju set mycharm foo=baz`
178 >>> # now we're inside subsequent config-changed hook
179 >>> config = hookenv.config()
180 >>> config['foo']
181 'baz'
182 >>> # test to see if this val has changed since last hook
183 >>> config.changed('foo')
184 True
185 >>> # what was the previous value?
186 >>> config.previous('foo')
187 'bar'
188 >>> # keys/values that we add are preserved across hooks
189 >>> config['mykey']
190 'myval'
191 >>> # don't forget to save at the end of hook!
192 >>> config.save()
193
194 """
195 CONFIG_FILE_NAME = '.juju-persistent-config'
196
197 def __init__(self, *args, **kw):
198 super(Config, self).__init__(*args, **kw)
199 self._prev_dict = None
200 self.path = os.path.join(charm_dir(), Config.CONFIG_FILE_NAME)
201 if os.path.exists(self.path):
202 self.load_previous()
203
204 def load_previous(self, path=None):
205 """Load previous copy of config from disk so that current values
206 can be compared to previous values.
207
208 :param path:
209
210 File path from which to load the previous config. If `None`,
211 config is loaded from the default location. If `path` is
212 specified, subsequent `save()` calls will write to the same
213 path.
214
215 """
216 self.path = path or self.path
217 with open(self.path) as f:
218 self._prev_dict = json.load(f)
219
220 def changed(self, key):
221 """Return true if the value for this key has changed since
222 the last save.
223
224 """
225 if self._prev_dict is None:
226 return True
227 return self.previous(key) != self.get(key)
228
229 def previous(self, key):
230 """Return previous value for this key, or None if there
231 is no "previous" value.
232
233 """
234 if self._prev_dict:
235 return self._prev_dict.get(key)
236 return None
237
238 def save(self):
239 """Save this config to disk.
240
241 Preserves items in _prev_dict that do not exist in self.
242
243 """
244 if self._prev_dict:
245 for k, v in self._prev_dict.iteritems():
246 if k not in self:
247 self[k] = v
248 with open(self.path, 'w') as f:
249 json.dump(self, f)
250
251
158@cached252@cached
159def config(scope=None):253def config(scope=None):
160 """Juju charm configuration"""254 """Juju charm configuration"""
@@ -163,7 +257,10 @@
163 config_cmd_line.append(scope)257 config_cmd_line.append(scope)
164 config_cmd_line.append('--format=json')258 config_cmd_line.append('--format=json')
165 try:259 try:
166 return json.loads(subprocess.check_output(config_cmd_line))260 config_data = json.loads(subprocess.check_output(config_cmd_line))
261 if scope is not None:
262 return config_data
263 return Config(config_data)
167 except ValueError:264 except ValueError:
168 return None265 return None
169266
@@ -348,18 +445,19 @@
348class Hooks(object):445class Hooks(object):
349 """A convenient handler for hook functions.446 """A convenient handler for hook functions.
350447
351 Example:448 Example::
449
352 hooks = Hooks()450 hooks = Hooks()
353451
354 # register a hook, taking its name from the function name452 # register a hook, taking its name from the function name
355 @hooks.hook()453 @hooks.hook()
356 def install():454 def install():
357 ...455 pass # your code here
358456
359 # register a hook, providing a custom hook name457 # register a hook, providing a custom hook name
360 @hooks.hook("config-changed")458 @hooks.hook("config-changed")
361 def config_changed():459 def config_changed():
362 ...460 pass # your code here
363461
364 if __name__ == "__main__":462 if __name__ == "__main__":
365 # execute a hook based on the name the program is called by463 # execute a hook based on the name the program is called by
366464
=== modified file 'hooks/charmhelpers/core/host.py'
--- hooks/charmhelpers/core/host.py 2014-04-11 20:55:42 +0000
+++ hooks/charmhelpers/core/host.py 2014-07-30 17:48:38 +0000
@@ -16,6 +16,7 @@
16from collections import OrderedDict16from collections import OrderedDict
1717
18from hookenv import log18from hookenv import log
19from fstab import Fstab
1920
2021
21def service_start(service_name):22def service_start(service_name):
@@ -34,7 +35,8 @@
3435
3536
36def service_reload(service_name, restart_on_failure=False):37def service_reload(service_name, restart_on_failure=False):
37 """Reload a system service, optionally falling back to restart if reload fails"""38 """Reload a system service, optionally falling back to restart if
39 reload fails"""
38 service_result = service('reload', service_name)40 service_result = service('reload', service_name)
39 if not service_result and restart_on_failure:41 if not service_result and restart_on_failure:
40 service_result = service('restart', service_name)42 service_result = service('restart', service_name)
@@ -143,7 +145,19 @@
143 target.write(content)145 target.write(content)
144146
145147
146def mount(device, mountpoint, options=None, persist=False):148def fstab_remove(mp):
149 """Remove the given mountpoint entry from /etc/fstab
150 """
151 return Fstab.remove_by_mountpoint(mp)
152
153
154def fstab_add(dev, mp, fs, options=None):
155 """Adds the given device entry to the /etc/fstab file
156 """
157 return Fstab.add(dev, mp, fs, options=options)
158
159
160def mount(device, mountpoint, options=None, persist=False, filesystem="ext3"):
147 """Mount a filesystem at a particular mountpoint"""161 """Mount a filesystem at a particular mountpoint"""
148 cmd_args = ['mount']162 cmd_args = ['mount']
149 if options is not None:163 if options is not None:
@@ -154,9 +168,9 @@
154 except subprocess.CalledProcessError, e:168 except subprocess.CalledProcessError, e:
155 log('Error mounting {} at {}\n{}'.format(device, mountpoint, e.output))169 log('Error mounting {} at {}\n{}'.format(device, mountpoint, e.output))
156 return False170 return False
171
157 if persist:172 if persist:
158 # TODO: update fstab173 return fstab_add(device, mountpoint, filesystem, options=options)
159 pass
160 return True174 return True
161175
162176
@@ -168,9 +182,9 @@
168 except subprocess.CalledProcessError, e:182 except subprocess.CalledProcessError, e:
169 log('Error unmounting {}\n{}'.format(mountpoint, e.output))183 log('Error unmounting {}\n{}'.format(mountpoint, e.output))
170 return False184 return False
185
171 if persist:186 if persist:
172 # TODO: update fstab187 return fstab_remove(mountpoint)
173 pass
174 return True188 return True
175189
176190
@@ -197,13 +211,13 @@
197def restart_on_change(restart_map, stopstart=False):211def restart_on_change(restart_map, stopstart=False):
198 """Restart services based on configuration files changing212 """Restart services based on configuration files changing
199213
200 This function is used a decorator, for example214 This function is used a decorator, for example::
201215
202 @restart_on_change({216 @restart_on_change({
203 '/etc/ceph/ceph.conf': [ 'cinder-api', 'cinder-volume' ]217 '/etc/ceph/ceph.conf': [ 'cinder-api', 'cinder-volume' ]
204 })218 })
205 def ceph_client_changed():219 def ceph_client_changed():
206 ...220 pass # your code here
207221
208 In this example, the cinder-api and cinder-volume services222 In this example, the cinder-api and cinder-volume services
209 would be restarted if /etc/ceph/ceph.conf is changed by the223 would be restarted if /etc/ceph/ceph.conf is changed by the
@@ -295,3 +309,19 @@
295 if 'link/ether' in words:309 if 'link/ether' in words:
296 hwaddr = words[words.index('link/ether') + 1]310 hwaddr = words[words.index('link/ether') + 1]
297 return hwaddr311 return hwaddr
312
313
314def cmp_pkgrevno(package, revno, pkgcache=None):
315 '''Compare supplied revno with the revno of the installed package
316
317 * 1 => Installed revno is greater than supplied arg
318 * 0 => Installed revno is the same as supplied arg
319 * -1 => Installed revno is less than supplied arg
320
321 '''
322 import apt_pkg
323 if not pkgcache:
324 apt_pkg.init()
325 pkgcache = apt_pkg.Cache()
326 pkg = pkgcache[package]
327 return apt_pkg.version_compare(pkg.current_ver.ver_str, revno)
298328
=== modified file 'hooks/charmhelpers/fetch/__init__.py'
--- hooks/charmhelpers/fetch/__init__.py 2014-04-11 20:55:42 +0000
+++ hooks/charmhelpers/fetch/__init__.py 2014-07-30 17:48:38 +0000
@@ -1,4 +1,5 @@
1import importlib1import importlib
2import time
2from yaml import safe_load3from yaml import safe_load
3from charmhelpers.core.host import (4from charmhelpers.core.host import (
4 lsb_release5 lsb_release
@@ -12,9 +13,9 @@
12 config,13 config,
13 log,14 log,
14)15)
15import apt_pkg
16import os16import os
1717
18
18CLOUD_ARCHIVE = """# Ubuntu Cloud Archive19CLOUD_ARCHIVE = """# Ubuntu Cloud Archive
19deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main20deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main
20"""21"""
@@ -54,12 +55,74 @@
54 'icehouse/proposed': 'precise-proposed/icehouse',55 'icehouse/proposed': 'precise-proposed/icehouse',
55 'precise-icehouse/proposed': 'precise-proposed/icehouse',56 'precise-icehouse/proposed': 'precise-proposed/icehouse',
56 'precise-proposed/icehouse': 'precise-proposed/icehouse',57 'precise-proposed/icehouse': 'precise-proposed/icehouse',
58 # Juno
59 'juno': 'trusty-updates/juno',
60 'trusty-juno': 'trusty-updates/juno',
61 'trusty-juno/updates': 'trusty-updates/juno',
62 'trusty-updates/juno': 'trusty-updates/juno',
63 'juno/proposed': 'trusty-proposed/juno',
64 'juno/proposed': 'trusty-proposed/juno',
65 'trusty-juno/proposed': 'trusty-proposed/juno',
66 'trusty-proposed/juno': 'trusty-proposed/juno',
57}67}
5868
69# The order of this list is very important. Handlers should be listed in from
70# least- to most-specific URL matching.
71FETCH_HANDLERS = (
72 'charmhelpers.fetch.archiveurl.ArchiveUrlFetchHandler',
73 'charmhelpers.fetch.bzrurl.BzrUrlFetchHandler',
74)
75
76APT_NO_LOCK = 100 # The return code for "couldn't acquire lock" in APT.
77APT_NO_LOCK_RETRY_DELAY = 10 # Wait 10 seconds between apt lock checks.
78APT_NO_LOCK_RETRY_COUNT = 30 # Retry to acquire the lock X times.
79
80
81class SourceConfigError(Exception):
82 pass
83
84
85class UnhandledSource(Exception):
86 pass
87
88
89class AptLockError(Exception):
90 pass
91
92
93class BaseFetchHandler(object):
94
95 """Base class for FetchHandler implementations in fetch plugins"""
96
97 def can_handle(self, source):
98 """Returns True if the source can be handled. Otherwise returns
99 a string explaining why it cannot"""
100 return "Wrong source type"
101
102 def install(self, source):
103 """Try to download and unpack the source. Return the path to the
104 unpacked files or raise UnhandledSource."""
105 raise UnhandledSource("Wrong source type {}".format(source))
106
107 def parse_url(self, url):
108 return urlparse(url)
109
110 def base_url(self, url):
111 """Return url without querystring or fragment"""
112 parts = list(self.parse_url(url))
113 parts[4:] = ['' for i in parts[4:]]
114 return urlunparse(parts)
115
59116
60def filter_installed_packages(packages):117def filter_installed_packages(packages):
61 """Returns a list of packages that require installation"""118 """Returns a list of packages that require installation"""
119 import apt_pkg
62 apt_pkg.init()120 apt_pkg.init()
121
122 # Tell apt to build an in-memory cache to prevent race conditions (if
123 # another process is already building the cache).
124 apt_pkg.config.set("Dir::Cache::pkgcache", "")
125
63 cache = apt_pkg.Cache()126 cache = apt_pkg.Cache()
64 _pkgs = []127 _pkgs = []
65 for package in packages:128 for package in packages:
@@ -87,14 +150,7 @@
87 cmd.extend(packages)150 cmd.extend(packages)
88 log("Installing {} with options: {}".format(packages,151 log("Installing {} with options: {}".format(packages,
89 options))152 options))
90 env = os.environ.copy()153 _run_apt_command(cmd, fatal)
91 if 'DEBIAN_FRONTEND' not in env:
92 env['DEBIAN_FRONTEND'] = 'noninteractive'
93
94 if fatal:
95 subprocess.check_call(cmd, env=env)
96 else:
97 subprocess.call(cmd, env=env)
98154
99155
100def apt_upgrade(options=None, fatal=False, dist=False):156def apt_upgrade(options=None, fatal=False, dist=False):
@@ -109,24 +165,13 @@
109 else:165 else:
110 cmd.append('upgrade')166 cmd.append('upgrade')
111 log("Upgrading with options: {}".format(options))167 log("Upgrading with options: {}".format(options))
112168 _run_apt_command(cmd, fatal)
113 env = os.environ.copy()
114 if 'DEBIAN_FRONTEND' not in env:
115 env['DEBIAN_FRONTEND'] = 'noninteractive'
116
117 if fatal:
118 subprocess.check_call(cmd, env=env)
119 else:
120 subprocess.call(cmd, env=env)
121169
122170
123def apt_update(fatal=False):171def apt_update(fatal=False):
124 """Update local apt cache"""172 """Update local apt cache"""
125 cmd = ['apt-get', 'update']173 cmd = ['apt-get', 'update']
126 if fatal:174 _run_apt_command(cmd, fatal)
127 subprocess.check_call(cmd)
128 else:
129 subprocess.call(cmd)
130175
131176
132def apt_purge(packages, fatal=False):177def apt_purge(packages, fatal=False):
@@ -137,10 +182,7 @@
137 else:182 else:
138 cmd.extend(packages)183 cmd.extend(packages)
139 log("Purging {}".format(packages))184 log("Purging {}".format(packages))
140 if fatal:185 _run_apt_command(cmd, fatal)
141 subprocess.check_call(cmd)
142 else:
143 subprocess.call(cmd)
144186
145187
146def apt_hold(packages, fatal=False):188def apt_hold(packages, fatal=False):
@@ -151,6 +193,7 @@
151 else:193 else:
152 cmd.extend(packages)194 cmd.extend(packages)
153 log("Holding {}".format(packages))195 log("Holding {}".format(packages))
196
154 if fatal:197 if fatal:
155 subprocess.check_call(cmd)198 subprocess.check_call(cmd)
156 else:199 else:
@@ -184,57 +227,50 @@
184 apt.write(PROPOSED_POCKET.format(release))227 apt.write(PROPOSED_POCKET.format(release))
185 if key:228 if key:
186 subprocess.check_call(['apt-key', 'adv', '--keyserver',229 subprocess.check_call(['apt-key', 'adv', '--keyserver',
187 'keyserver.ubuntu.com', '--recv',230 'hkp://keyserver.ubuntu.com:80', '--recv',
188 key])231 key])
189232
190233
191class SourceConfigError(Exception):
192 pass
193
194
195def configure_sources(update=False,234def configure_sources(update=False,
196 sources_var='install_sources',235 sources_var='install_sources',
197 keys_var='install_keys'):236 keys_var='install_keys'):
198 """237 """
199 Configure multiple sources from charm configuration238 Configure multiple sources from charm configuration.
239
240 The lists are encoded as yaml fragments in the configuration.
241 The frament needs to be included as a string.
200242
201 Example config:243 Example config:
202 install_sources:244 install_sources: |
203 - "ppa:foo"245 - "ppa:foo"
204 - "http://example.com/repo precise main"246 - "http://example.com/repo precise main"
205 install_keys:247 install_keys: |
206 - null248 - null
207 - "a1b2c3d4"249 - "a1b2c3d4"
208250
209 Note that 'null' (a.k.a. None) should not be quoted.251 Note that 'null' (a.k.a. None) should not be quoted.
210 """252 """
211 sources = safe_load(config(sources_var))253 sources = safe_load((config(sources_var) or '').strip()) or []
212 keys = config(keys_var)254 keys = safe_load((config(keys_var) or '').strip()) or None
213 if keys is not None:255
214 keys = safe_load(keys)256 if isinstance(sources, basestring):
215 if isinstance(sources, basestring) and (257 sources = [sources]
216 keys is None or isinstance(keys, basestring)):258
217 add_source(sources, keys)259 if keys is None:
260 for source in sources:
261 add_source(source, None)
218 else:262 else:
219 if not len(sources) == len(keys):263 if isinstance(keys, basestring):
220 msg = 'Install sources and keys lists are different lengths'264 keys = [keys]
221 raise SourceConfigError(msg)265
222 for src_num in range(len(sources)):266 if len(sources) != len(keys):
223 add_source(sources[src_num], keys[src_num])267 raise SourceConfigError(
268 'Install sources and keys lists are different lengths')
269 for source, key in zip(sources, keys):
270 add_source(source, key)
224 if update:271 if update:
225 apt_update(fatal=True)272 apt_update(fatal=True)
226273
227# The order of this list is very important. Handlers should be listed in from
228# least- to most-specific URL matching.
229FETCH_HANDLERS = (
230 'charmhelpers.fetch.archiveurl.ArchiveUrlFetchHandler',
231 'charmhelpers.fetch.bzrurl.BzrUrlFetchHandler',
232)
233
234
235class UnhandledSource(Exception):
236 pass
237
238274
239def install_remote(source):275def install_remote(source):
240 """276 """
@@ -265,30 +301,6 @@
265 return install_remote(source)301 return install_remote(source)
266302
267303
268class BaseFetchHandler(object):
269
270 """Base class for FetchHandler implementations in fetch plugins"""
271
272 def can_handle(self, source):
273 """Returns True if the source can be handled. Otherwise returns
274 a string explaining why it cannot"""
275 return "Wrong source type"
276
277 def install(self, source):
278 """Try to download and unpack the source. Return the path to the
279 unpacked files or raise UnhandledSource."""
280 raise UnhandledSource("Wrong source type {}".format(source))
281
282 def parse_url(self, url):
283 return urlparse(url)
284
285 def base_url(self, url):
286 """Return url without querystring or fragment"""
287 parts = list(self.parse_url(url))
288 parts[4:] = ['' for i in parts[4:]]
289 return urlunparse(parts)
290
291
292def plugins(fetch_handlers=None):304def plugins(fetch_handlers=None):
293 if not fetch_handlers:305 if not fetch_handlers:
294 fetch_handlers = FETCH_HANDLERS306 fetch_handlers = FETCH_HANDLERS
@@ -306,3 +318,40 @@
306 log("FetchHandler {} not found, skipping plugin".format(318 log("FetchHandler {} not found, skipping plugin".format(
307 handler_name))319 handler_name))
308 return plugin_list320 return plugin_list
321
322
323def _run_apt_command(cmd, fatal=False):
324 """
325 Run an APT command, checking output and retrying if the fatal flag is set
326 to True.
327
328 :param: cmd: str: The apt command to run.
329 :param: fatal: bool: Whether the command's output should be checked and
330 retried.
331 """
332 env = os.environ.copy()
333
334 if 'DEBIAN_FRONTEND' not in env:
335 env['DEBIAN_FRONTEND'] = 'noninteractive'
336
337 if fatal:
338 retry_count = 0
339 result = None
340
341 # If the command is considered "fatal", we need to retry if the apt
342 # lock was not acquired.
343
344 while result is None or result == APT_NO_LOCK:
345 try:
346 result = subprocess.check_call(cmd, env=env)
347 except subprocess.CalledProcessError, e:
348 retry_count = retry_count + 1
349 if retry_count > APT_NO_LOCK_RETRY_COUNT:
350 raise
351 result = e.returncode
352 log("Couldn't acquire DPKG lock. Will retry in {} seconds."
353 "".format(APT_NO_LOCK_RETRY_DELAY))
354 time.sleep(APT_NO_LOCK_RETRY_DELAY)
355
356 else:
357 subprocess.call(cmd, env=env)
309358
=== modified file 'hooks/charmhelpers/fetch/bzrurl.py'
--- hooks/charmhelpers/fetch/bzrurl.py 2014-04-11 20:55:42 +0000
+++ hooks/charmhelpers/fetch/bzrurl.py 2014-07-30 17:48:38 +0000
@@ -39,7 +39,8 @@
39 def install(self, source):39 def install(self, source):
40 url_parts = self.parse_url(source)40 url_parts = self.parse_url(source)
41 branch_name = url_parts.path.strip("/").split("/")[-1]41 branch_name = url_parts.path.strip("/").split("/")[-1]
42 dest_dir = os.path.join(os.environ.get('CHARM_DIR'), "fetched", branch_name)42 dest_dir = os.path.join(os.environ.get('CHARM_DIR'), "fetched",
43 branch_name)
43 if not os.path.exists(dest_dir):44 if not os.path.exists(dest_dir):
44 mkdir(dest_dir, perms=0755)45 mkdir(dest_dir, perms=0755)
45 try:46 try:
4647
=== modified file 'hooks/hooks.py'
--- hooks/hooks.py 2014-07-29 10:22:55 +0000
+++ hooks/hooks.py 2014-07-30 17:48:38 +0000
@@ -6,7 +6,6 @@
6'''6'''
77
8import commands8import commands
9import json
10import os9import os
11import re10import re
12import signal11import signal
@@ -15,7 +14,6 @@
15import sys14import sys
16import time15import time
17import yaml16import yaml
18import argparse
1917
20from os import chmod18from os import chmod
21from os import remove19from os import remove
@@ -29,10 +27,27 @@
29 apt_update,27 apt_update,
30 apt_install28 apt_install
31)29)
30
32from charmhelpers.core.hookenv import (31from charmhelpers.core.hookenv import (
33 config32 config,
34)33 unit_get,
3534 relation_get,
35 relation_set,
36 relations_of_type,
37 relation_id,
38 relation_ids,
39 open_port,
40 close_port,
41 Hooks,
42)
43
44from charmhelpers.core.hookenv import log as juju_log
45
46from charmhelpers.core.host import (
47 service,
48)
49
50hooks = Hooks()
3651
37###############################################################################52###############################################################################
38# Global variables53# Global variables
@@ -40,222 +55,14 @@
40default_mongodb_config = "/etc/mongodb.conf"55default_mongodb_config = "/etc/mongodb.conf"
41default_mongodb_init_config = "/etc/init/mongodb.conf"56default_mongodb_init_config = "/etc/init/mongodb.conf"
42default_mongos_list = "/etc/mongos.list"57default_mongos_list = "/etc/mongos.list"
43default_wait_for = 2058default_wait_for = 10
44default_max_tries = 2059default_max_tries = 5
4560
46###############################################################################61###############################################################################
47# Supporting functions62# Supporting functions
48###############################################################################63###############################################################################
4964
5065
51#------------------------------------------------------------------------------
52# juju_log: calls juju-log and records the message defined by the message
53# variable
54#------------------------------------------------------------------------------
55def juju_log(message=None):
56 return (subprocess.call(['juju-log', str(message)]) == 0)
57
58
59#------------------------------------------------------------------------------
60# service: Analogous to calling service on the command line to start/stop
61# and get status of a service/daemon.
62# Parameters:
63# service_name: The name of the service to act on.
64# service_action: The action (start, stop, status, etc.)
65# Returns: True if the command was successfully executed or False on
66# error.
67#------------------------------------------------------------------------------
68def service(service_name=None, service_action=None):
69 juju_log("service: %s, action: %s" % (service_name, service_action))
70 if service_name is not None and service_action is not None:
71 retVal = subprocess.call(
72 ["service", service_name, service_action]) == 0
73 else:
74 retVal = False
75 juju_log("service %s %s returns: %s" %
76 (service_name, service_action, retVal))
77 return(retVal)
78
79
80#------------------------------------------------------------------------------
81# unit_get: Convenience function wrapping the juju command unit-get
82# Parameter:
83# setting_name: The setting to get out of unit_get
84# Returns: The requested information or None on error
85#------------------------------------------------------------------------------
86def unit_get(setting_name=None):
87 juju_log("unit_get: %s" % setting_name)
88 try:
89 cmd_line = ['unit-get', '--format=json']
90 if setting_name is not None:
91 cmd_line.append(setting_name)
92 unit_data = json.loads(subprocess.check_output(cmd_line))
93 except Exception, e:
94 subprocess.call(['juju-log', str(e)])
95 unit_data = None
96 finally:
97 juju_log("unit_get %s returns: %s" % (setting_name, unit_data))
98 return(unit_data)
99
100
101#------------------------------------------------------------------------------
102# config_get: Returns a dictionary containing all of the config information
103# Optional parameter: scope
104# scope: limits the scope of the returned configuration to the
105# desired config item.
106#------------------------------------------------------------------------------
107def config_get(scope=None):
108 juju_log("config_get: %s" % scope)
109 try:
110 config_cmd_line = ['config-get']
111 if scope is not None:
112 config_cmd_line.append(scope)
113 config_cmd_line.append('--format=json')
114 config_data = json.loads(subprocess.check_output(config_cmd_line))
115 except Exception, e:
116 juju_log(str(e))
117 config_data = None
118 finally:
119 juju_log("config_get: %s returns: %s" % (scope, config_data))
120 return(config_data)
121
122
123#------------------------------------------------------------------------------
124# relation_get: Returns a dictionary containing the relation information
125# Optional parameters: scope, relation_id
126# scope: limits the scope of the returned data to the
127# desired item.
128# unit_name: limits the data ( and optionally the scope )
129# to the specified unit
130# relation_id: specify relation id for out of context usage.
131#------------------------------------------------------------------------------
132def relation_get(scope=None, unit_name=None, relation_id=None,
133 wait_for=default_wait_for, max_tries=default_max_tries):
134 juju_log("relation_get: scope: %s, unit_name: %s, relation_id: %s" %
135 (scope, unit_name, relation_id))
136 current_try = 0
137 try:
138 relation_cmd_line = ['relation-get', '--format=json']
139 if relation_id is not None:
140 relation_cmd_line.extend(('-r', relation_id))
141 if scope is not None:
142 relation_cmd_line.append(scope)
143 else:
144 relation_cmd_line.append('')
145 if unit_name is not None:
146 relation_cmd_line.append(unit_name)
147 relation_data = json.loads(subprocess.check_output(relation_cmd_line))
148
149# while relation_data is None and current_try < max_tries:
150# time.sleep(wait_for)
151# relation_data = json.loads(subprocess.check_output(relation_cmd_line))
152# current_try += 1
153
154 except Exception, e:
155 juju_log(str(e))
156 relation_data = None
157 finally:
158 juju_log("relation_get returns: %s" % relation_data)
159 return(relation_data)
160
161
162#------------------------------------------------------------------------------
163# relation_set: Convenience function wrapping the juju command relation-set
164# Parameters:
165# key_value_pairs: A dictionary containing the key/value pairs
166# to be set.
167# Optional Parameter:
168# relation_id: The relation id to use
169# Returns: True on success or False on failure
170#------------------------------------------------------------------------------
171def relation_set(key_value_pairs=None, relation_id=None):
172 juju_log("relation_set: kv: %s, relation_id: %s" %
173 (key_value_pairs, relation_id))
174 if key_value_pairs is None or not isinstance(key_value_pairs, dict):
175 juju_log("relation_set: Invalid key_value_pais.")
176 return(False)
177 try:
178 relation_cmd_line = ['relation-set', '--format=json']
179 if relation_id is not None:
180 relation_cmd_line.append('-r %s' % relation_id)
181 for (key, value) in key_value_pairs.items():
182 relation_cmd_line.append('%s=%s' % (key, value))
183 retVal = (subprocess.call(relation_cmd_line) == 0)
184 except Exception, e:
185 juju_log(str(e))
186 retVal = False
187 finally:
188 juju_log("relation_set returns: %s" % retVal)
189 return(retVal)
190
191
192def relation_list(relation_id=None, wait_for=default_wait_for,
193 max_tries=default_max_tries):
194 juju_log("relation_list: relation_id: %s" % relation_id)
195 current_try = 0
196 try:
197 relation_cmd_line = ['relation-list', '--format=json']
198 if relation_id is not None:
199 relation_cmd_line.append('-r %s' % relation_id)
200 relation_data = json.loads(subprocess.check_output(relation_cmd_line))
201
202# while relation_data is None and current_try < max_tries:
203# time.sleep(wait_for)
204# relation_data = json.loads(subprocess.check_output(relation_cmd_line))
205# current_try += 1
206
207 except Exception, e:
208 juju_log(str(e))
209 relation_data = None
210 finally:
211 juju_log("relation_id %s returns: %s" % (relation_id, relation_data))
212 return(relation_data)
213
214
215def relation_ids(relation_name=None):
216 juju_log("relation_ids: relation_name: %s" % relation_name)
217 try:
218 relation_cmd_line = ['relation-ids', '--format=json']
219 if relation_name is not None:
220 relation_cmd_line.append(relation_name)
221 relation_data = json.loads(subprocess.check_output(relation_cmd_line))
222 except Exception, e:
223 juju_log(str(e))
224 relation_data = None
225 finally:
226 juju_log("relation_ids %s returns: %s" % (relation_name, relation_data))
227 return(relation_data)
228
229#------------------------------------------------------------------------------
230# open_port: Convenience function to open a port in juju to
231# expose a service
232#------------------------------------------------------------------------------
233def open_port(port=None, protocol="TCP"):
234 juju_log("open_port: port: %d protocol: %s" % (int(port), protocol))
235 if port is None:
236 retVal = False
237 else:
238 retVal = subprocess.call(['open-port', "%d/%s" %
239 (int(port), protocol)]) == 0
240 juju_log("open_port %d/%s returns: %s" % (int(port), protocol, retVal))
241 return(retVal)
242
243
244#------------------------------------------------------------------------------
245# close_port: Convenience function to close a port in juju to
246# unexpose a service
247#------------------------------------------------------------------------------
248def close_port(port=None, protocol="TCP"):
249 juju_log("close_port: port: %d protocol: %s" % (int(port), protocol))
250 if port is None:
251 retVal = False
252 else:
253 retVal = subprocess.call(['close-port', "%d/%s" %
254 (int(port), protocol)]) == 0
255 juju_log("close_port %d/%s returns: %s" % (int(port), protocol, retVal))
256 return(retVal)
257
258
259def port_check(host=None, port=None, protocol='TCP'):66def port_check(host=None, port=None, protocol='TCP'):
260 if host is None or port is None:67 if host is None or port is None:
261 juju_log("port_check: host and port must be defined.")68 juju_log("port_check: host and port must be defined.")
@@ -494,7 +301,7 @@
494 config.append("")301 config.append("")
495302
496 # arbiter303 # arbiter
497 if config_data['arbiter'] != "disabled" and\304 if config_data['arbiter'] != "disabled" and \
498 config_data['arbiter'] != "enabled":305 config_data['arbiter'] != "enabled":
499 config.append("arbiter = %s" % config_data['arbiter'])306 config.append("arbiter = %s" % config_data['arbiter'])
500 config.append("")307 config.append("")
@@ -657,7 +464,7 @@
657464
658465
659def configsvr_status(wait_for=default_wait_for, max_tries=default_max_tries):466def configsvr_status(wait_for=default_wait_for, max_tries=default_max_tries):
660 config_data = config_get()467 config_data = config()
661 current_try = 0468 current_try = 0
662 while (process_check_pidfile('/var/run/mongodb/configsvr.pid') !=469 while (process_check_pidfile('/var/run/mongodb/configsvr.pid') !=
663 (None, None)) and not port_check(470 (None, None)) and not port_check(
@@ -685,7 +492,7 @@
685 juju_log("disable_configsvr: port not defined.")492 juju_log("disable_configsvr: port not defined.")
686 return(False)493 return(False)
687 try:494 try:
688 config_server_port = config_get('config_server_port')495 config_server_port = config('config_server_port')
689 pid = open('/var/run/mongodb/configsvr.pid').read()496 pid = open('/var/run/mongodb/configsvr.pid').read()
690 os.kill(int(pid), signal.SIGTERM)497 os.kill(int(pid), signal.SIGTERM)
691 os.unlink('/var/run/mongodb/configsvr.pid')498 os.unlink('/var/run/mongodb/configsvr.pid')
@@ -747,7 +554,7 @@
747554
748555
749def mongos_status(wait_for=default_wait_for, max_tries=default_max_tries):556def mongos_status(wait_for=default_wait_for, max_tries=default_max_tries):
750 config_data = config_get()557 config_data = config()
751 current_try = 0558 current_try = 0
752 while (process_check_pidfile('/var/run/mongodb/mongos.pid') !=559 while (process_check_pidfile('/var/run/mongodb/mongos.pid') !=
753 (None, None)) and not port_check(560 (None, None)) and not port_check(
@@ -839,17 +646,17 @@
839646
840def restart_mongod(wait_for=default_wait_for, max_tries=default_max_tries):647def restart_mongod(wait_for=default_wait_for, max_tries=default_max_tries):
841 my_hostname = unit_get('public-address')648 my_hostname = unit_get('public-address')
842 my_port = config_get('port')649 my_port = config('port')
843 current_try = 0650 current_try = 0
844651
845 service('mongodb', 'stop')652 service('stop', 'mongodb')
846 if os.path.exists('/var/lib/mongodb/mongod.lock'):653 if os.path.exists('/var/lib/mongodb/mongod.lock'):
847 os.remove('/var/lib/mongodb/mongod.lock')654 os.remove('/var/lib/mongodb/mongod.lock')
848655
849 if not service('mongodb', 'start'):656 if not service('start', 'mongodb'):
850 return False657 return False
851658
852 while (service('mongodb', 'status') and659 while (service('status', 'mongodb') and
853 not port_check(my_hostname, my_port) and660 not port_check(my_hostname, my_port) and
854 current_try < max_tries):661 current_try < max_tries):
855 juju_log(662 juju_log(
@@ -859,14 +666,14 @@
859 current_try += 1666 current_try += 1
860667
861 return(668 return(
862 (service('mongodb', 'status') == port_check(my_hostname, my_port))669 (service('status', 'mongodb') == port_check(my_hostname, my_port))
863 is True)670 is True)
864671
865672
866def backup_cronjob(disable=False):673def backup_cronjob(disable=False):
867 """Generate the cronjob to backup with mongodbump."""674 """Generate the cronjob to backup with mongodbump."""
868 juju_log('Setting up cronjob')675 juju_log('Setting up cronjob')
869 config_data = config_get()676 config_data = config()
870 backupdir = config_data['backup_directory']677 backupdir = config_data['backup_directory']
871 bind_ip = config_data['bind_ip']678 bind_ip = config_data['bind_ip']
872 cron_file = '/etc/cron.d/mongodb'679 cron_file = '/etc/cron.d/mongodb'
@@ -915,18 +722,19 @@
915###############################################################################722###############################################################################
916# Hook functions723# Hook functions
917###############################################################################724###############################################################################
725@hooks.hook('install')
918def install_hook():726def install_hook():
919 juju_log("Installing mongodb")727 juju_log("Installing mongodb")
920 add_source(config('source'), config('key'))728 add_source(config('source'), config('key'))
921 apt_update(fatal=True)729 apt_update(fatal=True)
922 apt_install(packages=['mongodb', 'python-yaml'], fatal=True)730 apt_install(packages=['mongodb', 'python-yaml'], fatal=True)
923 return True731
924732
925733@hooks.hook('config-changed')
926def config_changed():734def config_changed():
927 juju_log("Entering config_changed")735 juju_log("Entering config_changed")
928 print "Entering config_changed"736 print "Entering config_changed"
929 config_data = config_get()737 config_data = config()
930 print "config_data: ", config_data738 print "config_data: ", config_data
931 mongodb_config = open(default_mongodb_config).read()739 mongodb_config = open(default_mongodb_config).read()
932740
@@ -1048,7 +856,7 @@
1048 juju_log("config_changed: Exceptions: %s" % str(e))856 juju_log("config_changed: Exceptions: %s" % str(e))
1049857
1050 if mongos_pid is not None:858 if mongos_pid is not None:
1051 mongos_port = re.search('--port (\w+)', mongos_cmd_line).group(2)859 mongos_port = re.search('--port (\w+)', mongos_cmd_line).group(1)
1052 disable_mongos(mongos_port)860 disable_mongos(mongos_port)
1053 enable_mongos(config_data['mongos_port'])861 enable_mongos(config_data['mongos_port'])
1054 else:862 else:
@@ -1058,6 +866,7 @@
1058 return(True)866 return(True)
1059867
1060868
869@hooks.hook('start')
1061def start_hook():870def start_hook():
1062 juju_log("start_hook")871 juju_log("start_hook")
1063 retVal = restart_mongod()872 retVal = restart_mongod()
@@ -1065,10 +874,11 @@
1065 return(retVal)874 return(retVal)
1066875
1067876
877@hooks.hook('stop')
1068def stop_hook():878def stop_hook():
1069 juju_log("stop_hook")879 juju_log("stop_hook")
1070 try:880 try:
1071 retVal = service('mongodb', 'stop')881 retVal = service('stop', 'mongodb')
1072 os.remove('/var/lib/mongodb/mongod.lock')882 os.remove('/var/lib/mongodb/mongod.lock')
1073 #FIXME Need to check if this is still needed883 #FIXME Need to check if this is still needed
1074 except Exception, e:884 except Exception, e:
@@ -1079,15 +889,16 @@
1079 return(retVal)889 return(retVal)
1080890
1081891
892@hooks.hook('database-relation-joined')
1082def database_relation_joined():893def database_relation_joined():
1083 juju_log("database_relation_joined")894 juju_log("database_relation_joined")
1084 my_hostname = unit_get('public-address')895 my_hostname = unit_get('public-address')
1085 my_port = config_get('port')896 my_port = config('port')
1086 my_replset = config_get('replicaset')897 my_replset = config('replicaset')
1087 juju_log("my_hostname: %s" % my_hostname)898 juju_log("my_hostname: %s" % my_hostname)
1088 juju_log("my_port: %s" % my_port)899 juju_log("my_port: %s" % my_port)
1089 juju_log("my_replset: %s" % my_replset)900 juju_log("my_replset: %s" % my_replset)
1090 return(relation_set(901 return(relation_set(relation_id(),
1091 {902 {
1092 'hostname': my_hostname,903 'hostname': my_hostname,
1093 'port': my_port,904 'port': my_port,
@@ -1096,34 +907,36 @@
1096 }))907 }))
1097908
1098909
910@hooks.hook('replicaset-relation-joined')
1099def replica_set_relation_joined():911def replica_set_relation_joined():
1100 juju_log("replica_set_relation_joined")912 juju_log("replica_set_relation_joined")
1101 my_hostname = unit_get('public-address')913 my_hostname = unit_get('public-address')
1102 my_port = config_get('port')914 my_port = config('port')
1103 my_replset = config_get('replicaset')915 my_replset = config('replicaset')
1104 my_install_order = os.environ['JUJU_UNIT_NAME'].split('/')[1]916 my_install_order = os.environ['JUJU_UNIT_NAME'].split('/')[1]
1105 juju_log("my_hostname: %s" % my_hostname)917 juju_log("my_hostname: %s" % my_hostname)
1106 juju_log("my_port: %s" % my_port)918 juju_log("my_port: %s" % my_port)
1107 juju_log("my_replset: %s" % my_replset)919 juju_log("my_replset: %s" % my_replset)
1108 juju_log("my_install_order: %s" % my_install_order)920 juju_log("my_install_order: %s" % my_install_order)
1109 return(enable_replset(my_replset) ==921 enable_replset(my_replset)
1110 restart_mongod() ==922 restart_mongod()
1111 relation_set(923
1112 {924 relation_set(relation_id(), {
1113 'hostname': my_hostname,925 'hostname': my_hostname,
1114 'port': my_port,926 'port': my_port,
1115 'replset': my_replset,927 'replset': my_replset,
1116 'install-order': my_install_order,928 'install-order': my_install_order,
1117 'type': 'replset',929 'type': 'replset',
1118 }))930 })
1119931
1120932
933@hooks.hook('replicaset-relation-changed')
1121def replica_set_relation_changed():934def replica_set_relation_changed():
1122 juju_log("replica_set_relation_changed")935 juju_log("replica_set_relation_changed")
1123 my_hostname = unit_get('public-address')936 my_hostname = unit_get('public-address')
1124 my_port = config_get('port')937 my_port = config('port')
1125 my_install_order = os.environ['JUJU_UNIT_NAME'].split('/')[1]938 my_install_order = os.environ['JUJU_UNIT_NAME'].split('/')[1]
1126 my_replicaset_master = config_get('replicaset_master')939 my_replicaset_master = config('replicaset_master')
1127940
1128 # If we are joining an existing replicaset cluster, just join and leave.941 # If we are joining an existing replicaset cluster, just join and leave.
1129 if my_replicaset_master != "auto":942 if my_replicaset_master != "auto":
@@ -1135,44 +948,44 @@
1135 master_install_order = my_install_order948 master_install_order = my_install_order
1136949
1137 # Check the nodes in the relation to find the master950 # Check the nodes in the relation to find the master
1138 for member in relation_list():951 for member in relations_of_type('replica-set'):
952 member = member['__unit__']
1139 juju_log("replica_set_relation_changed: member: %s" % member)953 juju_log("replica_set_relation_changed: member: %s" % member)
1140 hostname = relation_get('hostname', member)954 hostname = relation_get('hostname', member)
1141 port = relation_get('port', member)955 port = relation_get('port', member)
1142 install_order = relation_get('install-order', member)956 inst_ordr = relation_get('install-order', member)
1143 juju_log("replica_set_relation_changed: install_order: %s" % install_order)957 juju_log("replica_set_relation_changed: install_order: %s" % inst_ordr)
1144 if install_order is None:958 if inst_ordr is None:
1145 juju_log("replica_set_relation_changed: install_order is None. relation is not ready")959 juju_log("replica_set_relation_changed: install_order is None."
960 " relation is not ready")
1146 break961 break
1147 if int(install_order) < int(master_install_order):962 if int(inst_ordr) < int(master_install_order):
1148 master_hostname = hostname963 master_hostname = hostname
1149 master_port = port964 master_port = port
1150 master_install_order = install_order965 master_install_order = inst_ordr
1151966
1152 # Initiate the replset967 # Initiate the replset
1153 init_replset("%s:%s" % (master_hostname, master_port))968 init_replset("%s:%s" % (master_hostname, master_port))
1154969
1155 # Add the rest of the nodes to the replset970 # Add the rest of the nodes to the replset
1156 for member in relation_list():971 for member in relations_of_type('replica-set'):
1157 hostname = relation_get('hostname', member)972 hostname = relation_get('hostname', member['__unit__'])
1158 port = relation_get('port', member)973 port = relation_get('port', member['__unit__'])
1159 if master_hostname != hostname:974 if master_hostname != hostname:
1160 if hostname == my_hostname:975 if hostname == my_hostname:
1161 subprocess.call(['mongo',976 subprocess.call(['mongo', '--eval',
1162 '--eval',977 "rs.add(\"%s\")" % hostname])
1163 "rs.add(\"%s\")" % hostname])
1164 else:978 else:
1165 join_replset("%s:%s" % (master_hostname, master_port),979 join_replset("%s:%s" % (master_hostname, master_port),
1166 "%s:%s" % (hostname, port))980 "%s:%s" % (hostname, port))
1167981
1168 # Add this node to the replset ( if needed )982 # Add this node to the replset ( if needed )
1169 if master_hostname != my_hostname:983 if master_hostname != my_hostname:
1170 join_replset("%s:%s" % (master_hostname, master_port),984 join_replset("%s:%s" % (master_hostname, master_port),
1171 "%s:%s" % (my_hostname, my_port))985 "%s:%s" % (my_hostname, my_port))
1172986
1173 return(True)987
1174988@hooks.hook('data-relation-joined')
1175
1176def data_relation_joined():989def data_relation_joined():
1177 juju_log("data_relation_joined")990 juju_log("data_relation_joined")
1178991
@@ -1182,6 +995,7 @@
1182 }))995 }))
1183996
1184997
998@hooks.hook('data-relation-changed')
1185def data_relation_changed():999def data_relation_changed():
1186 juju_log("data_relation_changed")1000 juju_log("data_relation_changed")
11871001
@@ -1189,60 +1003,63 @@
1189 juju_log("mountpoint from storage subordinate not ready, let's wait")1003 juju_log("mountpoint from storage subordinate not ready, let's wait")
1190 return(True)1004 return(True)
11911005
1192 return(config_changed())1006 config_changed()
11931007
11941008
1009@hooks.hook('data-relation-departed')
1195def data_relation_departed():1010def data_relation_departed():
1196 juju_log("data_relation_departed")1011 juju_log("data_relation_departed")
1197 return(config_changed())1012 return(config_changed())
11981013
11991014@hooks.hook('configsvr-relation-joined')
1200def configsvr_relation_joined():1015def configsvr_relation_joined():
1201 juju_log("configsvr_relation_joined")1016 juju_log("configsvr_relation_joined")
1202 my_hostname = unit_get('public-address')1017 my_hostname = unit_get('public-address')
1203 my_port = config_get('config_server_port')1018 my_port = config('config_server_port')
1204 my_install_order = os.environ['JUJU_UNIT_NAME'].split('/')[1]1019 my_install_order = os.environ['JUJU_UNIT_NAME'].split('/')[1]
1205 return(relation_set(1020 relation_set(relation_id(),
1206 {1021 {
1207 'hostname': my_hostname,1022 'hostname': my_hostname,
1208 'port': my_port,1023 'port': my_port,
1209 'install-order': my_install_order,1024 'install-order': my_install_order,
1210 'type': 'configsvr',1025 'type': 'configsvr',
1211 }))1026 })
12121027
12131028
1029@hooks.hook('configsvr-relation-changed')
1214def configsvr_relation_changed():1030def configsvr_relation_changed():
1215 juju_log("configsvr_relation_changed")1031 juju_log("configsvr_relation_changed")
1216 config_data = config_get()1032 config_data = config()
1217 my_port = config_data['config_server_port']1033 my_port = config_data['config_server_port']
1218 disable_configsvr(my_port)1034 disable_configsvr(my_port)
1219 retVal = enable_configsvr(config_data)1035
1220 juju_log("configsvr_relation_changed returns: %s" % retVal)1036
1221 return(retVal)1037@hooks.hook('mongos-cfg-relation-joined')
12221038@hooks.hook('mongos-relation-joined')
1223
1224def mongos_relation_joined():1039def mongos_relation_joined():
1225 juju_log("mongos_relation_joined")1040 juju_log("mongos_relation_joined")
1226 my_hostname = unit_get('public-address')1041 my_hostname = unit_get('public-address')
1227 my_port = config_get('mongos_port')1042 my_port = config('mongos_port')
1228 my_install_order = os.environ['JUJU_UNIT_NAME'].split('/')[1]1043 my_install_order = os.environ['JUJU_UNIT_NAME'].split('/')[1]
1229 return(relation_set(1044 relation_set(relation_id(),
1230 {1045 {
1231 'hostname': my_hostname,1046 'hostname': my_hostname,
1232 'port': my_port,1047 'port': my_port,
1233 'install-order': my_install_order,1048 'install-order': my_install_order,
1234 'type': 'mongos'1049 'type': 'mongos'
1235 }))1050 })
12361051
12371052
1053@hooks.hook('mongos-cfg-relation-changed')
1054@hooks.hook('mongos-relation-changed')
1238def mongos_relation_changed():1055def mongos_relation_changed():
1239 juju_log("mongos_relation_changed")1056 juju_log("mongos_relation_changed")
1240 config_data = config_get()1057 config_data = config()
1241 retVal = False1058 retVal = False
1242 for member in relation_list():1059 for member in relations_of_type('mongos-cfg'):
1243 hostname = relation_get('hostname', member)1060 hostname = relation_get('hostname', member['__unit__'])
1244 port = relation_get('port', member)1061 port = relation_get('port', member['__unit__'])
1245 rel_type = relation_get('type', member)1062 rel_type = relation_get('type', member['__unit__'])
1246 if hostname is None or port is None or rel_type is None:1063 if hostname is None or port is None or rel_type is None:
1247 juju_log("mongos_relation_changed: relation data not ready.")1064 juju_log("mongos_relation_changed: relation data not ready.")
1248 break1065 break
@@ -1264,34 +1081,33 @@
1264 if mongos_ready():1081 if mongos_ready():
1265 mongos_host = "%s:%s" % (1082 mongos_host = "%s:%s" % (
1266 unit_get('public-address'),1083 unit_get('public-address'),
1267 config_get('mongos_port'))1084 config('mongos_port'))
1268 shard_command1 = "sh.addShard(\"%s:%s\")" % (hostname, port)1085 shard_command1 = "sh.addShard(\"%s:%s\")" % (hostname, port)
1269 retVal1 = mongo_client(mongos_host, shard_command1)1086 mongo_client(mongos_host, shard_command1)
1270 replicaset = relation_get('replset', member)1087 replicaset = relation_get('replset', member)
1271 shard_command2 = "sh.addShard(\"%s/%s:%s\")" % \1088 shard_command2 = "sh.addShard(\"%s/%s:%s\")" % \
1272 (replicaset, hostname, port)1089 (replicaset, hostname, port)
1273 retVal2 = mongo_client(mongos_host, shard_command2)1090 mongo_client(mongos_host, shard_command2)
1274 retVal = retVal1 is True and retVal2 is True1091
1275 else:1092
1276 juju_log("Not enough config server for mongos yet.")
1277 retVal = True
1278 else:1093 else:
1279 juju_log("mongos_relation_change: undefined rel_type: %s" %1094 juju_log("mongos_relation_change: undefined rel_type: %s" %
1280 rel_type)1095 rel_type)
1281 return(False)1096 return(False)
1282 juju_log("mongos_relation_changed returns: %s" % retVal)1097 juju_log("mongos_relation_changed returns: %s" % retVal)
1283 return(retVal)1098
12841099
12851100
1101@hooks.hook('mongos-relation-broken')
1286def mongos_relation_broken():1102def mongos_relation_broken():
1287# config_servers = load_config_servers(default_mongos_list)1103 config_servers = load_config_servers(default_mongos_list)
1288# for member in relation_list():1104 for member in relations_of_type('mongos'):
1289# hostname = relation_get('hostname', member)1105 hostname = relation_get('hostname', member)
1290# port = relation_get('port', member)1106 port = relation_get('port', member)
1291# if '%s:%s' % (hostname, port) in config_servers:1107 if '%s:%s' % (hostname, port) in config_servers:
1292# config_servers.remove('%s:%s' % (hostname, port))1108 config_servers.remove('%s:%s' % (hostname, port))
1293# return(update_file(default_mongos_list, '\n'.join(config_servers)))1109
1294 return(True)1110 update_file(default_mongos_list, '\n'.join(config_servers))
12951111
12961112
1297def run(command, exit_on_error=True):1113def run(command, exit_on_error=True):
@@ -1318,7 +1134,7 @@
1318#1134#
1319#------------------------------1135#------------------------------
1320def volume_get_volid_from_volume_map():1136def volume_get_volid_from_volume_map():
1321 config_data = config_get()1137 config_data = config()
1322 volume_map = {}1138 volume_map = {}
1323 try:1139 try:
1324 volume_map = yaml.load(config_data['volume-map'].strip())1140 volume_map = yaml.load(config_data['volume-map'].strip())
@@ -1379,19 +1195,21 @@
1379# None config state is invalid - we should not serve1195# None config state is invalid - we should not serve
1380def volume_get_volume_id():1196def volume_get_volume_id():
13811197
1198 config_data = config()
1199
1200
13821201
1383 volid = volume_get_id_for_storage_subordinate()1202 volid = volume_get_id_for_storage_subordinate()
1384 if volid:1203 if volid:
1385 return volid1204 return volid
13861205
1387 config_data = config_get()
1388 ephemeral_storage = config_data['volume-ephemeral-storage']1206 ephemeral_storage = config_data['volume-ephemeral-storage']
1389 volid = volume_get_volid_from_volume_map()1207 volid = volume_get_volid_from_volume_map()
1390 juju_unit_name = os.environ['JUJU_UNIT_NAME']1208 juju_unit_name = os.environ['JUJU_UNIT_NAME']
1391 if ephemeral_storage in [True, 'yes', 'Yes', 'true', 'True']:1209 if ephemeral_storage in [True, 'yes', 'Yes', 'true', 'True']:
1392 if volid:1210 if volid:
1393 juju_log(1211 juju_log(
1394 "volume-ephemeral-storage is True, but " +1212 "volume-ephemeral-storage is True, but"
1395 "volume-map[{!r}] -> {}".format(juju_unit_name, volid))1213 "volume-map[{!r}] -> {}".format(juju_unit_name, volid))
1396 return None1214 return None
1397 else:1215 else:
@@ -1424,6 +1242,7 @@
1424 return None1242 return None
1425 return output1243 return output
14261244
1245
1427#------------------------------------------------------------------------------1246#------------------------------------------------------------------------------
1428# Core logic for permanent storage changes:1247# Core logic for permanent storage changes:
1429# NOTE the only 2 "True" return points:1248# NOTE the only 2 "True" return points:
@@ -1435,7 +1254,7 @@
1435# - manipulate /var/lib/mongodb/VERSION/CLUSTER symlink1254# - manipulate /var/lib/mongodb/VERSION/CLUSTER symlink
1436#------------------------------------------------------------------------------1255#------------------------------------------------------------------------------
1437def config_changed_volume_apply():1256def config_changed_volume_apply():
1438 config_data = config_get()1257 config_data = config()
1439 data_directory_path = config_data["dbpath"]1258 data_directory_path = config_data["dbpath"]
1440 assert(data_directory_path)1259 assert(data_directory_path)
1441 volid = volume_get_volume_id()1260 volid = volume_get_volume_id()
@@ -1548,57 +1367,6 @@
1548###############################################################################1367###############################################################################
1549# Main section1368# Main section
1550###############################################################################1369###############################################################################
1551if __name__ == '__main__':1370if __name__ == "__main__":
1552 parser = argparse.ArgumentParser()1371 # execute a hook based on the name the program is called by
1553 parser.add_argument('-H', '--hook_name', dest='hook_name',1372 hooks.execute(sys.argv)
1554 help='hook to call')
1555 args = parser.parse_args()
1556 if args.hook_name is not None:
1557 hook_name = args.hook_name
1558 else:
1559 hook_name = os.path.basename(sys.argv[0])
1560
1561 if hook_name == "install":
1562 retVal = install_hook()
1563 elif hook_name == "config-changed":
1564 retVal = config_changed()
1565 elif hook_name == "start":
1566 retVal = start_hook()
1567 elif hook_name == "stop":
1568 retVal = stop_hook()
1569 elif hook_name == "database-relation-joined":
1570 retVal = database_relation_joined()
1571 elif hook_name == "replica-set-relation-joined":
1572 retVal = replica_set_relation_joined()
1573 elif hook_name == "replica-set-relation-changed":
1574 retVal = replica_set_relation_changed()
1575 elif hook_name == "configsvr-relation-joined":
1576 retVal = configsvr_relation_joined()
1577 elif hook_name == "configsvr-relation-changed":
1578 retVal = configsvr_relation_changed()
1579 elif hook_name == "mongos-cfg-relation-joined":
1580 retVal = mongos_relation_joined()
1581 elif hook_name == "mongos-cfg-relation-changed":
1582 retVal = mongos_relation_changed()
1583 elif hook_name == "mongos-cfg-relation-broken":
1584 retVal = mongos_relation_broken()
1585 elif hook_name == "mongos-relation-joined":
1586 retVal = mongos_relation_joined()
1587 elif hook_name == "mongos-relation-changed":
1588 retVal = mongos_relation_changed()
1589 elif hook_name == "mongos-relation-broken":
1590 retVal = mongos_relation_broken()
1591 elif hook_name == "data-relation-joined":
1592 retVal = data_relation_joined()
1593 elif hook_name == "data-relation-changed":
1594 retVal = data_relation_changed()
1595 elif hook_name == "data-relation-departed":
1596 retVal = data_relation_departed()
1597 else:
1598 print "Unknown hook"
1599 retVal = False
1600
1601 if retVal is True:
1602 sys.exit(0)
1603 else:
1604 sys.exit(1)
1605\ No newline at end of file1373\ No newline at end of file
16061374
=== modified file 'hooks/install'
--- hooks/install 2013-11-25 19:48:00 +0000
+++ hooks/install 1970-01-01 00:00:00 +0000
@@ -1,5 +0,0 @@
1#!/bin/bash
2
3sudo apt-get install "python-yaml"
4
5hooks/hooks.py -H install
60
=== target is u'hooks.py'
=== modified file 'metadata.yaml'
--- metadata.yaml 2014-06-18 11:13:54 +0000
+++ metadata.yaml 2014-07-30 17:48:38 +0000
@@ -1,6 +1,9 @@
1name: mongodb1name: mongodb
2maintainer: Juan Negron <juan.negron@canonical.com>2summary: An open-source document database, and the leading NoSQL database
3summary: MongoDB (from humongous) is an open-source document database3maintainers:
4 - Juan Negron <juan.negron@canonical.com>
5 - Marco Ceppi <marco@ceppi.net>
6 - Charles Butler <chuck@dasroot.net>
4description: |7description: |
5 MongoDB is a high-performance, open source, schema-free document-8 MongoDB is a high-performance, open source, schema-free document-
6 oriented data store that's easy to deploy, manage and use. It's9 oriented data store that's easy to deploy, manage and use. It's
710
=== modified file 'tests/00-setup'
--- tests/00-setup 2014-02-25 21:37:07 +0000
+++ tests/00-setup 2014-07-30 17:48:38 +0000
@@ -1,11 +1,11 @@
1#!/bin/bash1#!/bin/bash
22
3set -e
4
3sudo apt-get install python-setuptools -y5sudo apt-get install python-setuptools -y
46sudo add-apt-repository ppa:juju/stable -y
5if [ -f '/etc/apt.d/sources.list.d/juju-stable-precise.list' ]; then
6 sudo add-apt-repository ppa:juju/stable -y
7fi
87
9sudo apt-get update8sudo apt-get update
109
11sudo apt-get install amulet python3 python3-requests python3-pymongo juju-core charm-tools python-mock -y10
11sudo apt-get install amulet python3 python3-requests python3-pymongo juju-core charm-tools python-mock python-pymongo -y
1212
=== added file 'tests/200_relate_ceilometer.test'
--- tests/200_relate_ceilometer.test 1970-01-01 00:00:00 +0000
+++ tests/200_relate_ceilometer.test 2014-07-30 17:48:38 +0000
@@ -0,0 +1,43 @@
1#!/usr/bin/env python3
2
3import amulet
4import pdb
5
6class TestDeploy(object):
7
8 def __init__(self, time=2500):
9 # Attempt to load the deployment topology from a bundle.
10 self.deploy = amulet.Deployment(series="trusty")
11
12 # If something errored out, attempt to continue by
13 # manually specifying a standalone deployment
14 self.deploy.add('mongodb')
15 self.deploy.add('ceilometer', 'cs:trusty/ceilometer')
16 # send blank configs to finalize the objects in the deployment map
17 self.deploy.configure('mongodb', {})
18 self.deploy.configure('ceilometer', {})
19
20 self.deploy.relate('mongodb:database', 'ceilometer:shared-db')
21
22 try:
23 self.deploy.setup(time)
24 self.deploy.sentry.wait(time)
25 except:
26 amulet.raise_status(amulet.FAIL, msg="Environment standup timeout")
27 # sentry = self.deploy.sentry
28
29 def run(self):
30 for test in dir(self):
31 if test.startswith('test_'):
32 getattr(self, test)()
33
34 def test_mongo_relation(self):
35 unit = self.deploy.sentry.unit['ceilometer/0']
36 mongo = self.deploy.sentry.unit['mongodb/0'].info['public-address']
37 cont = unit.file_contents('/etc/ceilometer/ceilometer.conf')
38 if mongo not in cont:
39 amulet.raise_status(amulet.FAIL, "Unable to verify ceilometer cfg")
40
41if __name__ == '__main__':
42 runner = TestDeploy()
43 runner.run()

Subscribers

People subscribed via source and target branches