Merge lp:~1chb1n/charms/trusty/nova-compute-power/kilo into lp:~james-page/charms/trusty/nova-compute-power/redux
- Trusty Tahr (14.04)
- kilo
- Merge into redux
Proposed by
Ryan Beisner
Status: | Merged |
---|---|
Merged at revision: | 120 |
Proposed branch: | lp:~1chb1n/charms/trusty/nova-compute-power/kilo |
Merge into: | lp:~james-page/charms/trusty/nova-compute-power/redux |
Diff against target: |
1018 lines (+949/-4) 10 files modified
Makefile (+8/-2) bin/charm_helpers_sync.py (+253/-0) charm-helpers.yaml (+2/-0) hooks/charmhelpers/contrib/charmsupport/__init__.py (+15/-0) hooks/charmhelpers/contrib/charmsupport/nrpe.py (+360/-0) hooks/charmhelpers/contrib/charmsupport/volumes.py (+175/-0) hooks/charmhelpers/contrib/python/__init__.py (+15/-0) hooks/charmhelpers/contrib/python/packages.py (+119/-0) templates/kilo/neutron.conf (+1/-1) templates/kilo/nova.conf (+1/-1) |
To merge this branch: | bzr merge lp:~1chb1n/charms/trusty/nova-compute-power/kilo |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
James Page | Pending | ||
Review via email:
|
Commit message
Description of the change
Additional kilo enablement.
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'Makefile' |
2 | --- Makefile 2014-11-07 16:46:25 +0000 |
3 | +++ Makefile 2015-06-04 17:43:44 +0000 |
4 | @@ -5,5 +5,11 @@ |
5 | @flake8 --exclude hooks/charmhelpers hooks |
6 | @charm proof |
7 | |
8 | -sync: |
9 | - @charm-helper-sync -c charm-helpers.yaml |
10 | +bin/charm_helpers_sync.py: |
11 | + @mkdir -p bin |
12 | + @bzr cat lp:charm-helpers/tools/charm_helpers_sync/charm_helpers_sync.py \ |
13 | + > bin/charm_helpers_sync.py |
14 | + |
15 | +sync: bin/charm_helpers_sync.py |
16 | + @$(PYTHON) bin/charm_helpers_sync.py -c charm-helpers.yaml |
17 | + |
18 | |
19 | === added directory 'bin' |
20 | === added file 'bin/charm_helpers_sync.py' |
21 | --- bin/charm_helpers_sync.py 1970-01-01 00:00:00 +0000 |
22 | +++ bin/charm_helpers_sync.py 2015-06-04 17:43:44 +0000 |
23 | @@ -0,0 +1,253 @@ |
24 | +#!/usr/bin/python |
25 | + |
26 | +# Copyright 2014-2015 Canonical Limited. |
27 | +# |
28 | +# This file is part of charm-helpers. |
29 | +# |
30 | +# charm-helpers is free software: you can redistribute it and/or modify |
31 | +# it under the terms of the GNU Lesser General Public License version 3 as |
32 | +# published by the Free Software Foundation. |
33 | +# |
34 | +# charm-helpers is distributed in the hope that it will be useful, |
35 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
36 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
37 | +# GNU Lesser General Public License for more details. |
38 | +# |
39 | +# You should have received a copy of the GNU Lesser General Public License |
40 | +# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
41 | + |
42 | +# Authors: |
43 | +# Adam Gandelman <adamg@ubuntu.com> |
44 | + |
45 | +import logging |
46 | +import optparse |
47 | +import os |
48 | +import subprocess |
49 | +import shutil |
50 | +import sys |
51 | +import tempfile |
52 | +import yaml |
53 | +from fnmatch import fnmatch |
54 | + |
55 | +import six |
56 | + |
57 | +CHARM_HELPERS_BRANCH = 'lp:charm-helpers' |
58 | + |
59 | + |
60 | +def parse_config(conf_file): |
61 | + if not os.path.isfile(conf_file): |
62 | + logging.error('Invalid config file: %s.' % conf_file) |
63 | + return False |
64 | + return yaml.load(open(conf_file).read()) |
65 | + |
66 | + |
67 | +def clone_helpers(work_dir, branch): |
68 | + dest = os.path.join(work_dir, 'charm-helpers') |
69 | + logging.info('Checking out %s to %s.' % (branch, dest)) |
70 | + cmd = ['bzr', 'checkout', '--lightweight', branch, dest] |
71 | + subprocess.check_call(cmd) |
72 | + return dest |
73 | + |
74 | + |
75 | +def _module_path(module): |
76 | + return os.path.join(*module.split('.')) |
77 | + |
78 | + |
79 | +def _src_path(src, module): |
80 | + return os.path.join(src, 'charmhelpers', _module_path(module)) |
81 | + |
82 | + |
83 | +def _dest_path(dest, module): |
84 | + return os.path.join(dest, _module_path(module)) |
85 | + |
86 | + |
87 | +def _is_pyfile(path): |
88 | + return os.path.isfile(path + '.py') |
89 | + |
90 | + |
91 | +def ensure_init(path): |
92 | + ''' |
93 | + ensure directories leading up to path are importable, omitting |
94 | + parent directory, eg path='/hooks/helpers/foo'/: |
95 | + hooks/ |
96 | + hooks/helpers/__init__.py |
97 | + hooks/helpers/foo/__init__.py |
98 | + ''' |
99 | + for d, dirs, files in os.walk(os.path.join(*path.split('/')[:2])): |
100 | + _i = os.path.join(d, '__init__.py') |
101 | + if not os.path.exists(_i): |
102 | + logging.info('Adding missing __init__.py: %s' % _i) |
103 | + open(_i, 'wb').close() |
104 | + |
105 | + |
106 | +def sync_pyfile(src, dest): |
107 | + src = src + '.py' |
108 | + src_dir = os.path.dirname(src) |
109 | + logging.info('Syncing pyfile: %s -> %s.' % (src, dest)) |
110 | + if not os.path.exists(dest): |
111 | + os.makedirs(dest) |
112 | + shutil.copy(src, dest) |
113 | + if os.path.isfile(os.path.join(src_dir, '__init__.py')): |
114 | + shutil.copy(os.path.join(src_dir, '__init__.py'), |
115 | + dest) |
116 | + ensure_init(dest) |
117 | + |
118 | + |
119 | +def get_filter(opts=None): |
120 | + opts = opts or [] |
121 | + if 'inc=*' in opts: |
122 | + # do not filter any files, include everything |
123 | + return None |
124 | + |
125 | + def _filter(dir, ls): |
126 | + incs = [opt.split('=').pop() for opt in opts if 'inc=' in opt] |
127 | + _filter = [] |
128 | + for f in ls: |
129 | + _f = os.path.join(dir, f) |
130 | + |
131 | + if not os.path.isdir(_f) and not _f.endswith('.py') and incs: |
132 | + if True not in [fnmatch(_f, inc) for inc in incs]: |
133 | + logging.debug('Not syncing %s, does not match include ' |
134 | + 'filters (%s)' % (_f, incs)) |
135 | + _filter.append(f) |
136 | + else: |
137 | + logging.debug('Including file, which matches include ' |
138 | + 'filters (%s): %s' % (incs, _f)) |
139 | + elif (os.path.isfile(_f) and not _f.endswith('.py')): |
140 | + logging.debug('Not syncing file: %s' % f) |
141 | + _filter.append(f) |
142 | + elif (os.path.isdir(_f) and not |
143 | + os.path.isfile(os.path.join(_f, '__init__.py'))): |
144 | + logging.debug('Not syncing directory: %s' % f) |
145 | + _filter.append(f) |
146 | + return _filter |
147 | + return _filter |
148 | + |
149 | + |
150 | +def sync_directory(src, dest, opts=None): |
151 | + if os.path.exists(dest): |
152 | + logging.debug('Removing existing directory: %s' % dest) |
153 | + shutil.rmtree(dest) |
154 | + logging.info('Syncing directory: %s -> %s.' % (src, dest)) |
155 | + |
156 | + shutil.copytree(src, dest, ignore=get_filter(opts)) |
157 | + ensure_init(dest) |
158 | + |
159 | + |
160 | +def sync(src, dest, module, opts=None): |
161 | + |
162 | + # Sync charmhelpers/__init__.py for bootstrap code. |
163 | + sync_pyfile(_src_path(src, '__init__'), dest) |
164 | + |
165 | + # Sync other __init__.py files in the path leading to module. |
166 | + m = [] |
167 | + steps = module.split('.')[:-1] |
168 | + while steps: |
169 | + m.append(steps.pop(0)) |
170 | + init = '.'.join(m + ['__init__']) |
171 | + sync_pyfile(_src_path(src, init), |
172 | + os.path.dirname(_dest_path(dest, init))) |
173 | + |
174 | + # Sync the module, or maybe a .py file. |
175 | + if os.path.isdir(_src_path(src, module)): |
176 | + sync_directory(_src_path(src, module), _dest_path(dest, module), opts) |
177 | + elif _is_pyfile(_src_path(src, module)): |
178 | + sync_pyfile(_src_path(src, module), |
179 | + os.path.dirname(_dest_path(dest, module))) |
180 | + else: |
181 | + logging.warn('Could not sync: %s. Neither a pyfile or directory, ' |
182 | + 'does it even exist?' % module) |
183 | + |
184 | + |
185 | +def parse_sync_options(options): |
186 | + if not options: |
187 | + return [] |
188 | + return options.split(',') |
189 | + |
190 | + |
191 | +def extract_options(inc, global_options=None): |
192 | + global_options = global_options or [] |
193 | + if global_options and isinstance(global_options, six.string_types): |
194 | + global_options = [global_options] |
195 | + if '|' not in inc: |
196 | + return (inc, global_options) |
197 | + inc, opts = inc.split('|') |
198 | + return (inc, parse_sync_options(opts) + global_options) |
199 | + |
200 | + |
201 | +def sync_helpers(include, src, dest, options=None): |
202 | + if not os.path.isdir(dest): |
203 | + os.makedirs(dest) |
204 | + |
205 | + global_options = parse_sync_options(options) |
206 | + |
207 | + for inc in include: |
208 | + if isinstance(inc, str): |
209 | + inc, opts = extract_options(inc, global_options) |
210 | + sync(src, dest, inc, opts) |
211 | + elif isinstance(inc, dict): |
212 | + # could also do nested dicts here. |
213 | + for k, v in six.iteritems(inc): |
214 | + if isinstance(v, list): |
215 | + for m in v: |
216 | + inc, opts = extract_options(m, global_options) |
217 | + sync(src, dest, '%s.%s' % (k, inc), opts) |
218 | + |
219 | +if __name__ == '__main__': |
220 | + parser = optparse.OptionParser() |
221 | + parser.add_option('-c', '--config', action='store', dest='config', |
222 | + default=None, help='helper config file') |
223 | + parser.add_option('-D', '--debug', action='store_true', dest='debug', |
224 | + default=False, help='debug') |
225 | + parser.add_option('-b', '--branch', action='store', dest='branch', |
226 | + help='charm-helpers bzr branch (overrides config)') |
227 | + parser.add_option('-d', '--destination', action='store', dest='dest_dir', |
228 | + help='sync destination dir (overrides config)') |
229 | + (opts, args) = parser.parse_args() |
230 | + |
231 | + if opts.debug: |
232 | + logging.basicConfig(level=logging.DEBUG) |
233 | + else: |
234 | + logging.basicConfig(level=logging.INFO) |
235 | + |
236 | + if opts.config: |
237 | + logging.info('Loading charm helper config from %s.' % opts.config) |
238 | + config = parse_config(opts.config) |
239 | + if not config: |
240 | + logging.error('Could not parse config from %s.' % opts.config) |
241 | + sys.exit(1) |
242 | + else: |
243 | + config = {} |
244 | + |
245 | + if 'branch' not in config: |
246 | + config['branch'] = CHARM_HELPERS_BRANCH |
247 | + if opts.branch: |
248 | + config['branch'] = opts.branch |
249 | + if opts.dest_dir: |
250 | + config['destination'] = opts.dest_dir |
251 | + |
252 | + if 'destination' not in config: |
253 | + logging.error('No destination dir. specified as option or config.') |
254 | + sys.exit(1) |
255 | + |
256 | + if 'include' not in config: |
257 | + if not args: |
258 | + logging.error('No modules to sync specified as option or config.') |
259 | + sys.exit(1) |
260 | + config['include'] = [] |
261 | + [config['include'].append(a) for a in args] |
262 | + |
263 | + sync_options = None |
264 | + if 'options' in config: |
265 | + sync_options = config['options'] |
266 | + tmpd = tempfile.mkdtemp() |
267 | + try: |
268 | + checkout = clone_helpers(tmpd, config['branch']) |
269 | + sync_helpers(config['include'], checkout, config['destination'], |
270 | + options=sync_options) |
271 | + except Exception as e: |
272 | + logging.error("Could not sync: %s" % e) |
273 | + raise e |
274 | + finally: |
275 | + logging.debug('Cleaning up %s' % tmpd) |
276 | + shutil.rmtree(tmpd) |
277 | |
278 | === modified file 'charm-helpers.yaml' |
279 | --- charm-helpers.yaml 2014-11-07 16:42:23 +0000 |
280 | +++ charm-helpers.yaml 2015-06-04 17:43:44 +0000 |
281 | @@ -9,4 +9,6 @@ |
282 | - apache |
283 | - cluster |
284 | - contrib.network |
285 | + - contrib.python.packages |
286 | - payload.execd |
287 | + - contrib.charmsupport |
288 | |
289 | === added directory 'hooks/charmhelpers/contrib/charmsupport' |
290 | === added file 'hooks/charmhelpers/contrib/charmsupport/__init__.py' |
291 | --- hooks/charmhelpers/contrib/charmsupport/__init__.py 1970-01-01 00:00:00 +0000 |
292 | +++ hooks/charmhelpers/contrib/charmsupport/__init__.py 2015-06-04 17:43:44 +0000 |
293 | @@ -0,0 +1,15 @@ |
294 | +# Copyright 2014-2015 Canonical Limited. |
295 | +# |
296 | +# This file is part of charm-helpers. |
297 | +# |
298 | +# charm-helpers is free software: you can redistribute it and/or modify |
299 | +# it under the terms of the GNU Lesser General Public License version 3 as |
300 | +# published by the Free Software Foundation. |
301 | +# |
302 | +# charm-helpers is distributed in the hope that it will be useful, |
303 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
304 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
305 | +# GNU Lesser General Public License for more details. |
306 | +# |
307 | +# You should have received a copy of the GNU Lesser General Public License |
308 | +# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
309 | |
310 | === added file 'hooks/charmhelpers/contrib/charmsupport/nrpe.py' |
311 | --- hooks/charmhelpers/contrib/charmsupport/nrpe.py 1970-01-01 00:00:00 +0000 |
312 | +++ hooks/charmhelpers/contrib/charmsupport/nrpe.py 2015-06-04 17:43:44 +0000 |
313 | @@ -0,0 +1,360 @@ |
314 | +# Copyright 2014-2015 Canonical Limited. |
315 | +# |
316 | +# This file is part of charm-helpers. |
317 | +# |
318 | +# charm-helpers is free software: you can redistribute it and/or modify |
319 | +# it under the terms of the GNU Lesser General Public License version 3 as |
320 | +# published by the Free Software Foundation. |
321 | +# |
322 | +# charm-helpers is distributed in the hope that it will be useful, |
323 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
324 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
325 | +# GNU Lesser General Public License for more details. |
326 | +# |
327 | +# You should have received a copy of the GNU Lesser General Public License |
328 | +# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
329 | + |
330 | +"""Compatibility with the nrpe-external-master charm""" |
331 | +# Copyright 2012 Canonical Ltd. |
332 | +# |
333 | +# Authors: |
334 | +# Matthew Wedgwood <matthew.wedgwood@canonical.com> |
335 | + |
336 | +import subprocess |
337 | +import pwd |
338 | +import grp |
339 | +import os |
340 | +import glob |
341 | +import shutil |
342 | +import re |
343 | +import shlex |
344 | +import yaml |
345 | + |
346 | +from charmhelpers.core.hookenv import ( |
347 | + config, |
348 | + local_unit, |
349 | + log, |
350 | + relation_ids, |
351 | + relation_set, |
352 | + relations_of_type, |
353 | +) |
354 | + |
355 | +from charmhelpers.core.host import service |
356 | + |
357 | +# This module adds compatibility with the nrpe-external-master and plain nrpe |
358 | +# subordinate charms. To use it in your charm: |
359 | +# |
360 | +# 1. Update metadata.yaml |
361 | +# |
362 | +# provides: |
363 | +# (...) |
364 | +# nrpe-external-master: |
365 | +# interface: nrpe-external-master |
366 | +# scope: container |
367 | +# |
368 | +# and/or |
369 | +# |
370 | +# provides: |
371 | +# (...) |
372 | +# local-monitors: |
373 | +# interface: local-monitors |
374 | +# scope: container |
375 | + |
376 | +# |
377 | +# 2. Add the following to config.yaml |
378 | +# |
379 | +# nagios_context: |
380 | +# default: "juju" |
381 | +# type: string |
382 | +# description: | |
383 | +# Used by the nrpe subordinate charms. |
384 | +# A string that will be prepended to instance name to set the host name |
385 | +# in nagios. So for instance the hostname would be something like: |
386 | +# juju-myservice-0 |
387 | +# If you're running multiple environments with the same services in them |
388 | +# this allows you to differentiate between them. |
389 | +# nagios_servicegroups: |
390 | +# default: "" |
391 | +# type: string |
392 | +# description: | |
393 | +# A comma-separated list of nagios servicegroups. |
394 | +# If left empty, the nagios_context will be used as the servicegroup |
395 | +# |
396 | +# 3. Add custom checks (Nagios plugins) to files/nrpe-external-master |
397 | +# |
398 | +# 4. Update your hooks.py with something like this: |
399 | +# |
400 | +# from charmsupport.nrpe import NRPE |
401 | +# (...) |
402 | +# def update_nrpe_config(): |
403 | +# nrpe_compat = NRPE() |
404 | +# nrpe_compat.add_check( |
405 | +# shortname = "myservice", |
406 | +# description = "Check MyService", |
407 | +# check_cmd = "check_http -w 2 -c 10 http://localhost" |
408 | +# ) |
409 | +# nrpe_compat.add_check( |
410 | +# "myservice_other", |
411 | +# "Check for widget failures", |
412 | +# check_cmd = "/srv/myapp/scripts/widget_check" |
413 | +# ) |
414 | +# nrpe_compat.write() |
415 | +# |
416 | +# def config_changed(): |
417 | +# (...) |
418 | +# update_nrpe_config() |
419 | +# |
420 | +# def nrpe_external_master_relation_changed(): |
421 | +# update_nrpe_config() |
422 | +# |
423 | +# def local_monitors_relation_changed(): |
424 | +# update_nrpe_config() |
425 | +# |
426 | +# 5. ln -s hooks.py nrpe-external-master-relation-changed |
427 | +# ln -s hooks.py local-monitors-relation-changed |
428 | + |
429 | + |
430 | +class CheckException(Exception): |
431 | + pass |
432 | + |
433 | + |
434 | +class Check(object): |
435 | + shortname_re = '[A-Za-z0-9-_]+$' |
436 | + service_template = (""" |
437 | +#--------------------------------------------------- |
438 | +# This file is Juju managed |
439 | +#--------------------------------------------------- |
440 | +define service {{ |
441 | + use active-service |
442 | + host_name {nagios_hostname} |
443 | + service_description {nagios_hostname}[{shortname}] """ |
444 | + """{description} |
445 | + check_command check_nrpe!{command} |
446 | + servicegroups {nagios_servicegroup} |
447 | +}} |
448 | +""") |
449 | + |
450 | + def __init__(self, shortname, description, check_cmd): |
451 | + super(Check, self).__init__() |
452 | + # XXX: could be better to calculate this from the service name |
453 | + if not re.match(self.shortname_re, shortname): |
454 | + raise CheckException("shortname must match {}".format( |
455 | + Check.shortname_re)) |
456 | + self.shortname = shortname |
457 | + self.command = "check_{}".format(shortname) |
458 | + # Note: a set of invalid characters is defined by the |
459 | + # Nagios server config |
460 | + # The default is: illegal_object_name_chars=`~!$%^&*"|'<>?,()= |
461 | + self.description = description |
462 | + self.check_cmd = self._locate_cmd(check_cmd) |
463 | + |
464 | + def _locate_cmd(self, check_cmd): |
465 | + search_path = ( |
466 | + '/usr/lib/nagios/plugins', |
467 | + '/usr/local/lib/nagios/plugins', |
468 | + ) |
469 | + parts = shlex.split(check_cmd) |
470 | + for path in search_path: |
471 | + if os.path.exists(os.path.join(path, parts[0])): |
472 | + command = os.path.join(path, parts[0]) |
473 | + if len(parts) > 1: |
474 | + command += " " + " ".join(parts[1:]) |
475 | + return command |
476 | + log('Check command not found: {}'.format(parts[0])) |
477 | + return '' |
478 | + |
479 | + def write(self, nagios_context, hostname, nagios_servicegroups): |
480 | + nrpe_check_file = '/etc/nagios/nrpe.d/{}.cfg'.format( |
481 | + self.command) |
482 | + with open(nrpe_check_file, 'w') as nrpe_check_config: |
483 | + nrpe_check_config.write("# check {}\n".format(self.shortname)) |
484 | + nrpe_check_config.write("command[{}]={}\n".format( |
485 | + self.command, self.check_cmd)) |
486 | + |
487 | + if not os.path.exists(NRPE.nagios_exportdir): |
488 | + log('Not writing service config as {} is not accessible'.format( |
489 | + NRPE.nagios_exportdir)) |
490 | + else: |
491 | + self.write_service_config(nagios_context, hostname, |
492 | + nagios_servicegroups) |
493 | + |
494 | + def write_service_config(self, nagios_context, hostname, |
495 | + nagios_servicegroups): |
496 | + for f in os.listdir(NRPE.nagios_exportdir): |
497 | + if re.search('.*{}.cfg'.format(self.command), f): |
498 | + os.remove(os.path.join(NRPE.nagios_exportdir, f)) |
499 | + |
500 | + templ_vars = { |
501 | + 'nagios_hostname': hostname, |
502 | + 'nagios_servicegroup': nagios_servicegroups, |
503 | + 'description': self.description, |
504 | + 'shortname': self.shortname, |
505 | + 'command': self.command, |
506 | + } |
507 | + nrpe_service_text = Check.service_template.format(**templ_vars) |
508 | + nrpe_service_file = '{}/service__{}_{}.cfg'.format( |
509 | + NRPE.nagios_exportdir, hostname, self.command) |
510 | + with open(nrpe_service_file, 'w') as nrpe_service_config: |
511 | + nrpe_service_config.write(str(nrpe_service_text)) |
512 | + |
513 | + def run(self): |
514 | + subprocess.call(self.check_cmd) |
515 | + |
516 | + |
517 | +class NRPE(object): |
518 | + nagios_logdir = '/var/log/nagios' |
519 | + nagios_exportdir = '/var/lib/nagios/export' |
520 | + nrpe_confdir = '/etc/nagios/nrpe.d' |
521 | + |
522 | + def __init__(self, hostname=None): |
523 | + super(NRPE, self).__init__() |
524 | + self.config = config() |
525 | + self.nagios_context = self.config['nagios_context'] |
526 | + if 'nagios_servicegroups' in self.config and self.config['nagios_servicegroups']: |
527 | + self.nagios_servicegroups = self.config['nagios_servicegroups'] |
528 | + else: |
529 | + self.nagios_servicegroups = self.nagios_context |
530 | + self.unit_name = local_unit().replace('/', '-') |
531 | + if hostname: |
532 | + self.hostname = hostname |
533 | + else: |
534 | + self.hostname = "{}-{}".format(self.nagios_context, self.unit_name) |
535 | + self.checks = [] |
536 | + |
537 | + def add_check(self, *args, **kwargs): |
538 | + self.checks.append(Check(*args, **kwargs)) |
539 | + |
540 | + def write(self): |
541 | + try: |
542 | + nagios_uid = pwd.getpwnam('nagios').pw_uid |
543 | + nagios_gid = grp.getgrnam('nagios').gr_gid |
544 | + except: |
545 | + log("Nagios user not set up, nrpe checks not updated") |
546 | + return |
547 | + |
548 | + if not os.path.exists(NRPE.nagios_logdir): |
549 | + os.mkdir(NRPE.nagios_logdir) |
550 | + os.chown(NRPE.nagios_logdir, nagios_uid, nagios_gid) |
551 | + |
552 | + nrpe_monitors = {} |
553 | + monitors = {"monitors": {"remote": {"nrpe": nrpe_monitors}}} |
554 | + for nrpecheck in self.checks: |
555 | + nrpecheck.write(self.nagios_context, self.hostname, |
556 | + self.nagios_servicegroups) |
557 | + nrpe_monitors[nrpecheck.shortname] = { |
558 | + "command": nrpecheck.command, |
559 | + } |
560 | + |
561 | + service('restart', 'nagios-nrpe-server') |
562 | + |
563 | + monitor_ids = relation_ids("local-monitors") + \ |
564 | + relation_ids("nrpe-external-master") |
565 | + for rid in monitor_ids: |
566 | + relation_set(relation_id=rid, monitors=yaml.dump(monitors)) |
567 | + |
568 | + |
569 | +def get_nagios_hostcontext(relation_name='nrpe-external-master'): |
570 | + """ |
571 | + Query relation with nrpe subordinate, return the nagios_host_context |
572 | + |
573 | + :param str relation_name: Name of relation nrpe sub joined to |
574 | + """ |
575 | + for rel in relations_of_type(relation_name): |
576 | + if 'nagios_hostname' in rel: |
577 | + return rel['nagios_host_context'] |
578 | + |
579 | + |
580 | +def get_nagios_hostname(relation_name='nrpe-external-master'): |
581 | + """ |
582 | + Query relation with nrpe subordinate, return the nagios_hostname |
583 | + |
584 | + :param str relation_name: Name of relation nrpe sub joined to |
585 | + """ |
586 | + for rel in relations_of_type(relation_name): |
587 | + if 'nagios_hostname' in rel: |
588 | + return rel['nagios_hostname'] |
589 | + |
590 | + |
591 | +def get_nagios_unit_name(relation_name='nrpe-external-master'): |
592 | + """ |
593 | + Return the nagios unit name prepended with host_context if needed |
594 | + |
595 | + :param str relation_name: Name of relation nrpe sub joined to |
596 | + """ |
597 | + host_context = get_nagios_hostcontext(relation_name) |
598 | + if host_context: |
599 | + unit = "%s:%s" % (host_context, local_unit()) |
600 | + else: |
601 | + unit = local_unit() |
602 | + return unit |
603 | + |
604 | + |
605 | +def add_init_service_checks(nrpe, services, unit_name): |
606 | + """ |
607 | + Add checks for each service in list |
608 | + |
609 | + :param NRPE nrpe: NRPE object to add check to |
610 | + :param list services: List of services to check |
611 | + :param str unit_name: Unit name to use in check description |
612 | + """ |
613 | + for svc in services: |
614 | + upstart_init = '/etc/init/%s.conf' % svc |
615 | + sysv_init = '/etc/init.d/%s' % svc |
616 | + if os.path.exists(upstart_init): |
617 | + nrpe.add_check( |
618 | + shortname=svc, |
619 | + description='process check {%s}' % unit_name, |
620 | + check_cmd='check_upstart_job %s' % svc |
621 | + ) |
622 | + elif os.path.exists(sysv_init): |
623 | + cronpath = '/etc/cron.d/nagios-service-check-%s' % svc |
624 | + cron_file = ('*/5 * * * * root ' |
625 | + '/usr/local/lib/nagios/plugins/check_exit_status.pl ' |
626 | + '-s /etc/init.d/%s status > ' |
627 | + '/var/lib/nagios/service-check-%s.txt\n' % (svc, |
628 | + svc) |
629 | + ) |
630 | + f = open(cronpath, 'w') |
631 | + f.write(cron_file) |
632 | + f.close() |
633 | + nrpe.add_check( |
634 | + shortname=svc, |
635 | + description='process check {%s}' % unit_name, |
636 | + check_cmd='check_status_file.py -f ' |
637 | + '/var/lib/nagios/service-check-%s.txt' % svc, |
638 | + ) |
639 | + |
640 | + |
641 | +def copy_nrpe_checks(): |
642 | + """ |
643 | + Copy the nrpe checks into place |
644 | + |
645 | + """ |
646 | + NAGIOS_PLUGINS = '/usr/local/lib/nagios/plugins' |
647 | + nrpe_files_dir = os.path.join(os.getenv('CHARM_DIR'), 'hooks', |
648 | + 'charmhelpers', 'contrib', 'openstack', |
649 | + 'files') |
650 | + |
651 | + if not os.path.exists(NAGIOS_PLUGINS): |
652 | + os.makedirs(NAGIOS_PLUGINS) |
653 | + for fname in glob.glob(os.path.join(nrpe_files_dir, "check_*")): |
654 | + if os.path.isfile(fname): |
655 | + shutil.copy2(fname, |
656 | + os.path.join(NAGIOS_PLUGINS, os.path.basename(fname))) |
657 | + |
658 | + |
659 | +def add_haproxy_checks(nrpe, unit_name): |
660 | + """ |
661 | + Add checks for each service in list |
662 | + |
663 | + :param NRPE nrpe: NRPE object to add check to |
664 | + :param str unit_name: Unit name to use in check description |
665 | + """ |
666 | + nrpe.add_check( |
667 | + shortname='haproxy_servers', |
668 | + description='Check HAProxy {%s}' % unit_name, |
669 | + check_cmd='check_haproxy.sh') |
670 | + nrpe.add_check( |
671 | + shortname='haproxy_queue', |
672 | + description='Check HAProxy queue depth {%s}' % unit_name, |
673 | + check_cmd='check_haproxy_queue_depth.sh') |
674 | |
675 | === added file 'hooks/charmhelpers/contrib/charmsupport/volumes.py' |
676 | --- hooks/charmhelpers/contrib/charmsupport/volumes.py 1970-01-01 00:00:00 +0000 |
677 | +++ hooks/charmhelpers/contrib/charmsupport/volumes.py 2015-06-04 17:43:44 +0000 |
678 | @@ -0,0 +1,175 @@ |
679 | +# Copyright 2014-2015 Canonical Limited. |
680 | +# |
681 | +# This file is part of charm-helpers. |
682 | +# |
683 | +# charm-helpers is free software: you can redistribute it and/or modify |
684 | +# it under the terms of the GNU Lesser General Public License version 3 as |
685 | +# published by the Free Software Foundation. |
686 | +# |
687 | +# charm-helpers is distributed in the hope that it will be useful, |
688 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
689 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
690 | +# GNU Lesser General Public License for more details. |
691 | +# |
692 | +# You should have received a copy of the GNU Lesser General Public License |
693 | +# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
694 | + |
695 | +''' |
696 | +Functions for managing volumes in juju units. One volume is supported per unit. |
697 | +Subordinates may have their own storage, provided it is on its own partition. |
698 | + |
699 | +Configuration stanzas:: |
700 | + |
701 | + volume-ephemeral: |
702 | + type: boolean |
703 | + default: true |
704 | + description: > |
705 | + If false, a volume is mounted as sepecified in "volume-map" |
706 | + If true, ephemeral storage will be used, meaning that log data |
707 | + will only exist as long as the machine. YOU HAVE BEEN WARNED. |
708 | + volume-map: |
709 | + type: string |
710 | + default: {} |
711 | + description: > |
712 | + YAML map of units to device names, e.g: |
713 | + "{ rsyslog/0: /dev/vdb, rsyslog/1: /dev/vdb }" |
714 | + Service units will raise a configure-error if volume-ephemeral |
715 | + is 'true' and no volume-map value is set. Use 'juju set' to set a |
716 | + value and 'juju resolved' to complete configuration. |
717 | + |
718 | +Usage:: |
719 | + |
720 | + from charmsupport.volumes import configure_volume, VolumeConfigurationError |
721 | + from charmsupport.hookenv import log, ERROR |
722 | + def post_mount_hook(): |
723 | + stop_service('myservice') |
724 | + def post_mount_hook(): |
725 | + start_service('myservice') |
726 | + |
727 | + if __name__ == '__main__': |
728 | + try: |
729 | + configure_volume(before_change=pre_mount_hook, |
730 | + after_change=post_mount_hook) |
731 | + except VolumeConfigurationError: |
732 | + log('Storage could not be configured', ERROR) |
733 | + |
734 | +''' |
735 | + |
736 | +# XXX: Known limitations |
737 | +# - fstab is neither consulted nor updated |
738 | + |
739 | +import os |
740 | +from charmhelpers.core import hookenv |
741 | +from charmhelpers.core import host |
742 | +import yaml |
743 | + |
744 | + |
745 | +MOUNT_BASE = '/srv/juju/volumes' |
746 | + |
747 | + |
748 | +class VolumeConfigurationError(Exception): |
749 | + '''Volume configuration data is missing or invalid''' |
750 | + pass |
751 | + |
752 | + |
753 | +def get_config(): |
754 | + '''Gather and sanity-check volume configuration data''' |
755 | + volume_config = {} |
756 | + config = hookenv.config() |
757 | + |
758 | + errors = False |
759 | + |
760 | + if config.get('volume-ephemeral') in (True, 'True', 'true', 'Yes', 'yes'): |
761 | + volume_config['ephemeral'] = True |
762 | + else: |
763 | + volume_config['ephemeral'] = False |
764 | + |
765 | + try: |
766 | + volume_map = yaml.safe_load(config.get('volume-map', '{}')) |
767 | + except yaml.YAMLError as e: |
768 | + hookenv.log("Error parsing YAML volume-map: {}".format(e), |
769 | + hookenv.ERROR) |
770 | + errors = True |
771 | + if volume_map is None: |
772 | + # probably an empty string |
773 | + volume_map = {} |
774 | + elif not isinstance(volume_map, dict): |
775 | + hookenv.log("Volume-map should be a dictionary, not {}".format( |
776 | + type(volume_map))) |
777 | + errors = True |
778 | + |
779 | + volume_config['device'] = volume_map.get(os.environ['JUJU_UNIT_NAME']) |
780 | + if volume_config['device'] and volume_config['ephemeral']: |
781 | + # asked for ephemeral storage but also defined a volume ID |
782 | + hookenv.log('A volume is defined for this unit, but ephemeral ' |
783 | + 'storage was requested', hookenv.ERROR) |
784 | + errors = True |
785 | + elif not volume_config['device'] and not volume_config['ephemeral']: |
786 | + # asked for permanent storage but did not define volume ID |
787 | + hookenv.log('Ephemeral storage was requested, but there is no volume ' |
788 | + 'defined for this unit.', hookenv.ERROR) |
789 | + errors = True |
790 | + |
791 | + unit_mount_name = hookenv.local_unit().replace('/', '-') |
792 | + volume_config['mountpoint'] = os.path.join(MOUNT_BASE, unit_mount_name) |
793 | + |
794 | + if errors: |
795 | + return None |
796 | + return volume_config |
797 | + |
798 | + |
799 | +def mount_volume(config): |
800 | + if os.path.exists(config['mountpoint']): |
801 | + if not os.path.isdir(config['mountpoint']): |
802 | + hookenv.log('Not a directory: {}'.format(config['mountpoint'])) |
803 | + raise VolumeConfigurationError() |
804 | + else: |
805 | + host.mkdir(config['mountpoint']) |
806 | + if os.path.ismount(config['mountpoint']): |
807 | + unmount_volume(config) |
808 | + if not host.mount(config['device'], config['mountpoint'], persist=True): |
809 | + raise VolumeConfigurationError() |
810 | + |
811 | + |
812 | +def unmount_volume(config): |
813 | + if os.path.ismount(config['mountpoint']): |
814 | + if not host.umount(config['mountpoint'], persist=True): |
815 | + raise VolumeConfigurationError() |
816 | + |
817 | + |
818 | +def managed_mounts(): |
819 | + '''List of all mounted managed volumes''' |
820 | + return filter(lambda mount: mount[0].startswith(MOUNT_BASE), host.mounts()) |
821 | + |
822 | + |
823 | +def configure_volume(before_change=lambda: None, after_change=lambda: None): |
824 | + '''Set up storage (or don't) according to the charm's volume configuration. |
825 | + Returns the mount point or "ephemeral". before_change and after_change |
826 | + are optional functions to be called if the volume configuration changes. |
827 | + ''' |
828 | + |
829 | + config = get_config() |
830 | + if not config: |
831 | + hookenv.log('Failed to read volume configuration', hookenv.CRITICAL) |
832 | + raise VolumeConfigurationError() |
833 | + |
834 | + if config['ephemeral']: |
835 | + if os.path.ismount(config['mountpoint']): |
836 | + before_change() |
837 | + unmount_volume(config) |
838 | + after_change() |
839 | + return 'ephemeral' |
840 | + else: |
841 | + # persistent storage |
842 | + if os.path.ismount(config['mountpoint']): |
843 | + mounts = dict(managed_mounts()) |
844 | + if mounts.get(config['mountpoint']) != config['device']: |
845 | + before_change() |
846 | + unmount_volume(config) |
847 | + mount_volume(config) |
848 | + after_change() |
849 | + else: |
850 | + before_change() |
851 | + mount_volume(config) |
852 | + after_change() |
853 | + return config['mountpoint'] |
854 | |
855 | === added directory 'hooks/charmhelpers/contrib/python' |
856 | === added file 'hooks/charmhelpers/contrib/python/__init__.py' |
857 | --- hooks/charmhelpers/contrib/python/__init__.py 1970-01-01 00:00:00 +0000 |
858 | +++ hooks/charmhelpers/contrib/python/__init__.py 2015-06-04 17:43:44 +0000 |
859 | @@ -0,0 +1,15 @@ |
860 | +# Copyright 2014-2015 Canonical Limited. |
861 | +# |
862 | +# This file is part of charm-helpers. |
863 | +# |
864 | +# charm-helpers is free software: you can redistribute it and/or modify |
865 | +# it under the terms of the GNU Lesser General Public License version 3 as |
866 | +# published by the Free Software Foundation. |
867 | +# |
868 | +# charm-helpers is distributed in the hope that it will be useful, |
869 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
870 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
871 | +# GNU Lesser General Public License for more details. |
872 | +# |
873 | +# You should have received a copy of the GNU Lesser General Public License |
874 | +# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
875 | |
876 | === added file 'hooks/charmhelpers/contrib/python/packages.py' |
877 | --- hooks/charmhelpers/contrib/python/packages.py 1970-01-01 00:00:00 +0000 |
878 | +++ hooks/charmhelpers/contrib/python/packages.py 2015-06-04 17:43:44 +0000 |
879 | @@ -0,0 +1,119 @@ |
880 | +#!/usr/bin/env python |
881 | +# coding: utf-8 |
882 | + |
883 | +# Copyright 2014-2015 Canonical Limited. |
884 | +# |
885 | +# This file is part of charm-helpers. |
886 | +# |
887 | +# charm-helpers is free software: you can redistribute it and/or modify |
888 | +# it under the terms of the GNU Lesser General Public License version 3 as |
889 | +# published by the Free Software Foundation. |
890 | +# |
891 | +# charm-helpers is distributed in the hope that it will be useful, |
892 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
893 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
894 | +# GNU Lesser General Public License for more details. |
895 | +# |
896 | +# You should have received a copy of the GNU Lesser General Public License |
897 | +# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
898 | + |
899 | +import os |
900 | +import subprocess |
901 | + |
902 | +from charmhelpers.fetch import apt_install, apt_update |
903 | +from charmhelpers.core.hookenv import charm_dir, log |
904 | + |
905 | +try: |
906 | + from pip import main as pip_execute |
907 | +except ImportError: |
908 | + apt_update() |
909 | + apt_install('python-pip') |
910 | + from pip import main as pip_execute |
911 | + |
912 | +__author__ = "Jorge Niedbalski <jorge.niedbalski@canonical.com>" |
913 | + |
914 | + |
915 | +def parse_options(given, available): |
916 | + """Given a set of options, check if available""" |
917 | + for key, value in sorted(given.items()): |
918 | + if key in available: |
919 | + yield "--{0}={1}".format(key, value) |
920 | + |
921 | + |
922 | +def pip_install_requirements(requirements, **options): |
923 | + """Install a requirements file """ |
924 | + command = ["install"] |
925 | + |
926 | + available_options = ('proxy', 'src', 'log', ) |
927 | + for option in parse_options(options, available_options): |
928 | + command.append(option) |
929 | + |
930 | + command.append("-r {0}".format(requirements)) |
931 | + log("Installing from file: {} with options: {}".format(requirements, |
932 | + command)) |
933 | + pip_execute(command) |
934 | + |
935 | + |
936 | +def pip_install(package, fatal=False, upgrade=False, venv=None, **options): |
937 | + """Install a python package""" |
938 | + if venv: |
939 | + venv_python = os.path.join(venv, 'bin/pip') |
940 | + command = [venv_python, "install"] |
941 | + else: |
942 | + command = ["install"] |
943 | + |
944 | + available_options = ('proxy', 'src', 'log', 'index-url', ) |
945 | + for option in parse_options(options, available_options): |
946 | + command.append(option) |
947 | + |
948 | + if upgrade: |
949 | + command.append('--upgrade') |
950 | + |
951 | + if isinstance(package, list): |
952 | + command.extend(package) |
953 | + else: |
954 | + command.append(package) |
955 | + |
956 | + log("Installing {} package with options: {}".format(package, |
957 | + command)) |
958 | + if venv: |
959 | + subprocess.check_call(command) |
960 | + else: |
961 | + pip_execute(command) |
962 | + |
963 | + |
964 | +def pip_uninstall(package, **options): |
965 | + """Uninstall a python package""" |
966 | + command = ["uninstall", "-q", "-y"] |
967 | + |
968 | + available_options = ('proxy', 'log', ) |
969 | + for option in parse_options(options, available_options): |
970 | + command.append(option) |
971 | + |
972 | + if isinstance(package, list): |
973 | + command.extend(package) |
974 | + else: |
975 | + command.append(package) |
976 | + |
977 | + log("Uninstalling {} package with options: {}".format(package, |
978 | + command)) |
979 | + pip_execute(command) |
980 | + |
981 | + |
982 | +def pip_list(): |
983 | + """Returns the list of current python installed packages |
984 | + """ |
985 | + return pip_execute(["list"]) |
986 | + |
987 | + |
988 | +def pip_create_virtualenv(path=None): |
989 | + """Create an isolated Python environment.""" |
990 | + apt_install('python-virtualenv') |
991 | + |
992 | + if path: |
993 | + venv_path = path |
994 | + else: |
995 | + venv_path = os.path.join(charm_dir(), 'venv') |
996 | + |
997 | + if not os.path.exists(venv_path): |
998 | + subprocess.check_call(['virtualenv', venv_path]) |
999 | |
1000 | === modified file 'templates/kilo/neutron.conf' |
1001 | --- templates/kilo/neutron.conf 2015-06-04 11:44:35 +0000 |
1002 | +++ templates/kilo/neutron.conf 2015-06-04 17:43:44 +0000 |
1003 | @@ -1,4 +1,4 @@ |
1004 | -# icehouse |
1005 | +# kilo |
1006 | ############################################################################### |
1007 | # [ WARNING ] |
1008 | # Configuration file maintained by Juju. Local changes may be overwritten. |
1009 | |
1010 | === modified file 'templates/kilo/nova.conf' |
1011 | --- templates/kilo/nova.conf 2015-06-04 11:44:35 +0000 |
1012 | +++ templates/kilo/nova.conf 2015-06-04 17:43:44 +0000 |
1013 | @@ -1,4 +1,4 @@ |
1014 | -# icehouse |
1015 | +# kilo |
1016 | ############################################################################### |
1017 | # [ WARNING ] |
1018 | # Configuration file maintained by Juju. Local changes may be overwritten. |