Merge lp:~1chb1n/charms/trusty/nova-compute-power/kilo into lp:~james-page/charms/trusty/nova-compute-power/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
Reviewer Review Type Date Requested Status
James Page Pending
Review via email: mp+261099@code.launchpad.net

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.

Subscribers

People subscribed via source and target branches

to all changes: