Merge lp:~lazypower/charms/trusty/mongodb/charmhelpers_merge into lp:charms/mongodb
- Trusty Tahr (14.04)
- charmhelpers_merge
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Marco Ceppi (community) | Approve | ||
Cory Johns (community) | Approve | ||
Review via email: mp+228758@code.launchpad.net |
Commit message
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
- 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() |
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.