Merge lp:~timkuhlman/charms/trusty/rsyslog-forwarder-ha/nrpe into lp:charms/trusty/rsyslog-forwarder-ha

Proposed by Tim Kuhlman on 2016-04-26
Status: Merged
Merge reported by: Stuart Bishop
Merged at revision: not available
Proposed branch: lp:~timkuhlman/charms/trusty/rsyslog-forwarder-ha/nrpe
Merge into: lp:charms/trusty/rsyslog-forwarder-ha
Diff against target: 768 lines (+645/-5)
9 files modified
charm-helpers.yaml (+1/-0)
config.yaml (+16/-0)
hooks/charmhelpers/contrib/charmsupport/__init__.py (+15/-0)
hooks/charmhelpers/contrib/charmsupport/nrpe.py (+398/-0)
hooks/charmhelpers/contrib/charmsupport/volumes.py (+175/-0)
hooks/hooks.py (+24/-2)
metadata.yaml (+4/-0)
templates/failover.template (+9/-0)
tests/unit/test_basic.py (+3/-3)
To merge this branch: bzr merge lp:~timkuhlman/charms/trusty/rsyslog-forwarder-ha/nrpe
Reviewer Review Type Date Requested Status
Stuart Bishop Approve on 2016-06-06
Andrew McLeod (community) Approve on 2016-05-19
Adam Israel 2016-04-26 Needs Fixing on 2016-05-19
Review Queue (community) automated testing Needs Fixing on 2016-05-06
Review via email: mp+292987@code.launchpad.net

Description of the Change

Adds an nrpe relation and nagios check for the rsyslog daemon.

To post a comment you must log in.
15. By Tim Kuhlman on 2016-04-28

Make tcp failover mode more robust especially on Juju controllers

Junien Fridrick (axino) wrote :

Perhaps the added "$ActionQueueDiscardSeverity" could be a charm option ?

16. By Tim Kuhlman on 2016-05-02

The default config logs locally so that even with log-locally set false that was happening. Fixed it so that log-locally overwrites the default config

17. By Tim Kuhlman on 2016-05-02

Restore a working default config on stop

18. By Tim Kuhlman on 2016-05-03

Set $ActionSendStreamDriver rather than resetting the default which breaks anything relying on the previous default

Review Queue (review-queue) wrote :

This item has failed automated testing! Results available here http://juju-ci.vapour.ws:8080/job/charm-bundle-test-aws/3949/

review: Needs Fixing (automated testing)
Review Queue (review-queue) wrote :

This item has failed automated testing! Results available here http://juju-ci.vapour.ws:8080/job/charm-bundle-test-aws/3950/

review: Needs Fixing (automated testing)
19. By Tim Kuhlman on 2016-05-04

Explicitly set tcp mode to non-ssl

Review Queue (review-queue) wrote :

This item has failed automated testing! Results available here http://juju-ci.vapour.ws:8080/job/charm-bundle-test-lxc/3904/

review: Needs Fixing (automated testing)
Review Queue (review-queue) wrote :

This item has failed automated testing! Results available here http://juju-ci.vapour.ws:8080/job/charm-bundle-test-lxc/3905/

review: Needs Fixing (automated testing)
Adam Israel (aisrael) wrote :

Hey Tim,

Since the current CI/review queue only keeps logs of the last 300 runs (which will be fixed in the new RQ), here's the output of my tests running against AWS and Juju 2 beta 7.

http://pastebin.ubuntu.com/16508154/

review: Needs Fixing
20. By Tim Kuhlman on 2016-05-19

Lint fixes and fix for stop test

Tim Kuhlman (timkuhlman) wrote :

Thanks I just pushed up the fixes.

On 05/19/2016 09:37 AM, Adam Israel wrote:
> Review: Needs Fixing
>
> Hey Tim,
>
> Since the current CI/review queue only keeps logs of the last 300 runs (which will be fixed in the new RQ), here's the output of my tests running against AWS and Juju 2 beta 7.
>
> http://pastebin.ubuntu.com/16508154/
>

--
Tim Kuhlman
CDO - IS - Foxtrot

Andrew McLeod (admcleod) wrote :

All tests passing, bundletester all OK, +1

review: Approve
Stuart Bishop (stub) wrote :

Looks good

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charm-helpers.yaml'
2--- charm-helpers.yaml 2014-04-25 22:18:46 +0000
3+++ charm-helpers.yaml 2016-05-19 16:36:23 +0000
4@@ -3,3 +3,4 @@
5 include:
6 - core
7 - fetch
8+ - contrib.charmsupport
9
10=== modified file 'config.yaml'
11--- config.yaml 2016-03-30 21:54:14 +0000
12+++ config.yaml 2016-05-19 16:36:23 +0000
13@@ -7,6 +7,22 @@
14 type: string
15 default: fanout
16 description: Possible options are 'fanout' or 'failover' fanout replicates the log messages over all the defined syslog relations, failover replicates the log messages just if the previous server is down. Failover always uses tcp for the protocol.
17+ nagios_context:
18+ default: "juju"
19+ type: string
20+ description: >
21+ Used by the nrpe-external-master subordinate charm.
22+ A string that will be prepended to instance name to set the host name
23+ in nagios. So for instance the hostname would be something like:
24+ juju-rsyslog-forwarder-ha-0
25+ If you're running multiple environments with the same services in them
26+ this allows you to differentiate between them.
27+ nagios_servicegroups:
28+ default: ""
29+ type: string
30+ description: >
31+ A comma-separated list of nagios servicegroups.
32+ If left empty, the nagios_context will be used as the servicegroup
33 protocol:
34 type: string
35 default: "udp"
36
37=== added directory 'hooks/charmhelpers/contrib'
38=== added file 'hooks/charmhelpers/contrib/__init__.py'
39=== added directory 'hooks/charmhelpers/contrib/charmsupport'
40=== added file 'hooks/charmhelpers/contrib/charmsupport/__init__.py'
41--- hooks/charmhelpers/contrib/charmsupport/__init__.py 1970-01-01 00:00:00 +0000
42+++ hooks/charmhelpers/contrib/charmsupport/__init__.py 2016-05-19 16:36:23 +0000
43@@ -0,0 +1,15 @@
44+# Copyright 2014-2015 Canonical Limited.
45+#
46+# This file is part of charm-helpers.
47+#
48+# charm-helpers is free software: you can redistribute it and/or modify
49+# it under the terms of the GNU Lesser General Public License version 3 as
50+# published by the Free Software Foundation.
51+#
52+# charm-helpers is distributed in the hope that it will be useful,
53+# but WITHOUT ANY WARRANTY; without even the implied warranty of
54+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
55+# GNU Lesser General Public License for more details.
56+#
57+# You should have received a copy of the GNU Lesser General Public License
58+# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
59
60=== added file 'hooks/charmhelpers/contrib/charmsupport/nrpe.py'
61--- hooks/charmhelpers/contrib/charmsupport/nrpe.py 1970-01-01 00:00:00 +0000
62+++ hooks/charmhelpers/contrib/charmsupport/nrpe.py 2016-05-19 16:36:23 +0000
63@@ -0,0 +1,398 @@
64+# Copyright 2014-2015 Canonical Limited.
65+#
66+# This file is part of charm-helpers.
67+#
68+# charm-helpers is free software: you can redistribute it and/or modify
69+# it under the terms of the GNU Lesser General Public License version 3 as
70+# published by the Free Software Foundation.
71+#
72+# charm-helpers is distributed in the hope that it will be useful,
73+# but WITHOUT ANY WARRANTY; without even the implied warranty of
74+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
75+# GNU Lesser General Public License for more details.
76+#
77+# You should have received a copy of the GNU Lesser General Public License
78+# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
79+
80+"""Compatibility with the nrpe-external-master charm"""
81+# Copyright 2012 Canonical Ltd.
82+#
83+# Authors:
84+# Matthew Wedgwood <matthew.wedgwood@canonical.com>
85+
86+import subprocess
87+import pwd
88+import grp
89+import os
90+import glob
91+import shutil
92+import re
93+import shlex
94+import yaml
95+
96+from charmhelpers.core.hookenv import (
97+ config,
98+ local_unit,
99+ log,
100+ relation_ids,
101+ relation_set,
102+ relations_of_type,
103+)
104+
105+from charmhelpers.core.host import service
106+
107+# This module adds compatibility with the nrpe-external-master and plain nrpe
108+# subordinate charms. To use it in your charm:
109+#
110+# 1. Update metadata.yaml
111+#
112+# provides:
113+# (...)
114+# nrpe-external-master:
115+# interface: nrpe-external-master
116+# scope: container
117+#
118+# and/or
119+#
120+# provides:
121+# (...)
122+# local-monitors:
123+# interface: local-monitors
124+# scope: container
125+
126+#
127+# 2. Add the following to config.yaml
128+#
129+# nagios_context:
130+# default: "juju"
131+# type: string
132+# description: |
133+# Used by the nrpe subordinate charms.
134+# A string that will be prepended to instance name to set the host name
135+# in nagios. So for instance the hostname would be something like:
136+# juju-myservice-0
137+# If you're running multiple environments with the same services in them
138+# this allows you to differentiate between them.
139+# nagios_servicegroups:
140+# default: ""
141+# type: string
142+# description: |
143+# A comma-separated list of nagios servicegroups.
144+# If left empty, the nagios_context will be used as the servicegroup
145+#
146+# 3. Add custom checks (Nagios plugins) to files/nrpe-external-master
147+#
148+# 4. Update your hooks.py with something like this:
149+#
150+# from charmsupport.nrpe import NRPE
151+# (...)
152+# def update_nrpe_config():
153+# nrpe_compat = NRPE()
154+# nrpe_compat.add_check(
155+# shortname = "myservice",
156+# description = "Check MyService",
157+# check_cmd = "check_http -w 2 -c 10 http://localhost"
158+# )
159+# nrpe_compat.add_check(
160+# "myservice_other",
161+# "Check for widget failures",
162+# check_cmd = "/srv/myapp/scripts/widget_check"
163+# )
164+# nrpe_compat.write()
165+#
166+# def config_changed():
167+# (...)
168+# update_nrpe_config()
169+#
170+# def nrpe_external_master_relation_changed():
171+# update_nrpe_config()
172+#
173+# def local_monitors_relation_changed():
174+# update_nrpe_config()
175+#
176+# 5. ln -s hooks.py nrpe-external-master-relation-changed
177+# ln -s hooks.py local-monitors-relation-changed
178+
179+
180+class CheckException(Exception):
181+ pass
182+
183+
184+class Check(object):
185+ shortname_re = '[A-Za-z0-9-_]+$'
186+ service_template = ("""
187+#---------------------------------------------------
188+# This file is Juju managed
189+#---------------------------------------------------
190+define service {{
191+ use active-service
192+ host_name {nagios_hostname}
193+ service_description {nagios_hostname}[{shortname}] """
194+ """{description}
195+ check_command check_nrpe!{command}
196+ servicegroups {nagios_servicegroup}
197+}}
198+""")
199+
200+ def __init__(self, shortname, description, check_cmd):
201+ super(Check, self).__init__()
202+ # XXX: could be better to calculate this from the service name
203+ if not re.match(self.shortname_re, shortname):
204+ raise CheckException("shortname must match {}".format(
205+ Check.shortname_re))
206+ self.shortname = shortname
207+ self.command = "check_{}".format(shortname)
208+ # Note: a set of invalid characters is defined by the
209+ # Nagios server config
210+ # The default is: illegal_object_name_chars=`~!$%^&*"|'<>?,()=
211+ self.description = description
212+ self.check_cmd = self._locate_cmd(check_cmd)
213+
214+ def _get_check_filename(self):
215+ return os.path.join(NRPE.nrpe_confdir, '{}.cfg'.format(self.command))
216+
217+ def _get_service_filename(self, hostname):
218+ return os.path.join(NRPE.nagios_exportdir,
219+ 'service__{}_{}.cfg'.format(hostname, self.command))
220+
221+ def _locate_cmd(self, check_cmd):
222+ search_path = (
223+ '/usr/lib/nagios/plugins',
224+ '/usr/local/lib/nagios/plugins',
225+ )
226+ parts = shlex.split(check_cmd)
227+ for path in search_path:
228+ if os.path.exists(os.path.join(path, parts[0])):
229+ command = os.path.join(path, parts[0])
230+ if len(parts) > 1:
231+ command += " " + " ".join(parts[1:])
232+ return command
233+ log('Check command not found: {}'.format(parts[0]))
234+ return ''
235+
236+ def _remove_service_files(self):
237+ if not os.path.exists(NRPE.nagios_exportdir):
238+ return
239+ for f in os.listdir(NRPE.nagios_exportdir):
240+ if f.endswith('_{}.cfg'.format(self.command)):
241+ os.remove(os.path.join(NRPE.nagios_exportdir, f))
242+
243+ def remove(self, hostname):
244+ nrpe_check_file = self._get_check_filename()
245+ if os.path.exists(nrpe_check_file):
246+ os.remove(nrpe_check_file)
247+ self._remove_service_files()
248+
249+ def write(self, nagios_context, hostname, nagios_servicegroups):
250+ nrpe_check_file = self._get_check_filename()
251+ with open(nrpe_check_file, 'w') as nrpe_check_config:
252+ nrpe_check_config.write("# check {}\n".format(self.shortname))
253+ nrpe_check_config.write("command[{}]={}\n".format(
254+ self.command, self.check_cmd))
255+
256+ if not os.path.exists(NRPE.nagios_exportdir):
257+ log('Not writing service config as {} is not accessible'.format(
258+ NRPE.nagios_exportdir))
259+ else:
260+ self.write_service_config(nagios_context, hostname,
261+ nagios_servicegroups)
262+
263+ def write_service_config(self, nagios_context, hostname,
264+ nagios_servicegroups):
265+ self._remove_service_files()
266+
267+ templ_vars = {
268+ 'nagios_hostname': hostname,
269+ 'nagios_servicegroup': nagios_servicegroups,
270+ 'description': self.description,
271+ 'shortname': self.shortname,
272+ 'command': self.command,
273+ }
274+ nrpe_service_text = Check.service_template.format(**templ_vars)
275+ nrpe_service_file = self._get_service_filename(hostname)
276+ with open(nrpe_service_file, 'w') as nrpe_service_config:
277+ nrpe_service_config.write(str(nrpe_service_text))
278+
279+ def run(self):
280+ subprocess.call(self.check_cmd)
281+
282+
283+class NRPE(object):
284+ nagios_logdir = '/var/log/nagios'
285+ nagios_exportdir = '/var/lib/nagios/export'
286+ nrpe_confdir = '/etc/nagios/nrpe.d'
287+
288+ def __init__(self, hostname=None):
289+ super(NRPE, self).__init__()
290+ self.config = config()
291+ self.nagios_context = self.config['nagios_context']
292+ if 'nagios_servicegroups' in self.config and self.config['nagios_servicegroups']:
293+ self.nagios_servicegroups = self.config['nagios_servicegroups']
294+ else:
295+ self.nagios_servicegroups = self.nagios_context
296+ self.unit_name = local_unit().replace('/', '-')
297+ if hostname:
298+ self.hostname = hostname
299+ else:
300+ nagios_hostname = get_nagios_hostname()
301+ if nagios_hostname:
302+ self.hostname = nagios_hostname
303+ else:
304+ self.hostname = "{}-{}".format(self.nagios_context, self.unit_name)
305+ self.checks = []
306+
307+ def add_check(self, *args, **kwargs):
308+ self.checks.append(Check(*args, **kwargs))
309+
310+ def remove_check(self, *args, **kwargs):
311+ if kwargs.get('shortname') is None:
312+ raise ValueError('shortname of check must be specified')
313+
314+ # Use sensible defaults if they're not specified - these are not
315+ # actually used during removal, but they're required for constructing
316+ # the Check object; check_disk is chosen because it's part of the
317+ # nagios-plugins-basic package.
318+ if kwargs.get('check_cmd') is None:
319+ kwargs['check_cmd'] = 'check_disk'
320+ if kwargs.get('description') is None:
321+ kwargs['description'] = ''
322+
323+ check = Check(*args, **kwargs)
324+ check.remove(self.hostname)
325+
326+ def write(self):
327+ try:
328+ nagios_uid = pwd.getpwnam('nagios').pw_uid
329+ nagios_gid = grp.getgrnam('nagios').gr_gid
330+ except:
331+ log("Nagios user not set up, nrpe checks not updated")
332+ return
333+
334+ if not os.path.exists(NRPE.nagios_logdir):
335+ os.mkdir(NRPE.nagios_logdir)
336+ os.chown(NRPE.nagios_logdir, nagios_uid, nagios_gid)
337+
338+ nrpe_monitors = {}
339+ monitors = {"monitors": {"remote": {"nrpe": nrpe_monitors}}}
340+ for nrpecheck in self.checks:
341+ nrpecheck.write(self.nagios_context, self.hostname,
342+ self.nagios_servicegroups)
343+ nrpe_monitors[nrpecheck.shortname] = {
344+ "command": nrpecheck.command,
345+ }
346+
347+ service('restart', 'nagios-nrpe-server')
348+
349+ monitor_ids = relation_ids("local-monitors") + \
350+ relation_ids("nrpe-external-master")
351+ for rid in monitor_ids:
352+ relation_set(relation_id=rid, monitors=yaml.dump(monitors))
353+
354+
355+def get_nagios_hostcontext(relation_name='nrpe-external-master'):
356+ """
357+ Query relation with nrpe subordinate, return the nagios_host_context
358+
359+ :param str relation_name: Name of relation nrpe sub joined to
360+ """
361+ for rel in relations_of_type(relation_name):
362+ if 'nagios_host_context' in rel:
363+ return rel['nagios_host_context']
364+
365+
366+def get_nagios_hostname(relation_name='nrpe-external-master'):
367+ """
368+ Query relation with nrpe subordinate, return the nagios_hostname
369+
370+ :param str relation_name: Name of relation nrpe sub joined to
371+ """
372+ for rel in relations_of_type(relation_name):
373+ if 'nagios_hostname' in rel:
374+ return rel['nagios_hostname']
375+
376+
377+def get_nagios_unit_name(relation_name='nrpe-external-master'):
378+ """
379+ Return the nagios unit name prepended with host_context if needed
380+
381+ :param str relation_name: Name of relation nrpe sub joined to
382+ """
383+ host_context = get_nagios_hostcontext(relation_name)
384+ if host_context:
385+ unit = "%s:%s" % (host_context, local_unit())
386+ else:
387+ unit = local_unit()
388+ return unit
389+
390+
391+def add_init_service_checks(nrpe, services, unit_name):
392+ """
393+ Add checks for each service in list
394+
395+ :param NRPE nrpe: NRPE object to add check to
396+ :param list services: List of services to check
397+ :param str unit_name: Unit name to use in check description
398+ """
399+ for svc in services:
400+ upstart_init = '/etc/init/%s.conf' % svc
401+ sysv_init = '/etc/init.d/%s' % svc
402+ if os.path.exists(upstart_init):
403+ # Don't add a check for these services from neutron-gateway
404+ if svc not in ['ext-port', 'os-charm-phy-nic-mtu']:
405+ nrpe.add_check(
406+ shortname=svc,
407+ description='process check {%s}' % unit_name,
408+ check_cmd='check_upstart_job %s' % svc
409+ )
410+ elif os.path.exists(sysv_init):
411+ cronpath = '/etc/cron.d/nagios-service-check-%s' % svc
412+ cron_file = ('*/5 * * * * root '
413+ '/usr/local/lib/nagios/plugins/check_exit_status.pl '
414+ '-s /etc/init.d/%s status > '
415+ '/var/lib/nagios/service-check-%s.txt\n' % (svc,
416+ svc)
417+ )
418+ f = open(cronpath, 'w')
419+ f.write(cron_file)
420+ f.close()
421+ nrpe.add_check(
422+ shortname=svc,
423+ description='process check {%s}' % unit_name,
424+ check_cmd='check_status_file.py -f '
425+ '/var/lib/nagios/service-check-%s.txt' % svc,
426+ )
427+
428+
429+def copy_nrpe_checks():
430+ """
431+ Copy the nrpe checks into place
432+
433+ """
434+ NAGIOS_PLUGINS = '/usr/local/lib/nagios/plugins'
435+ nrpe_files_dir = os.path.join(os.getenv('CHARM_DIR'), 'hooks',
436+ 'charmhelpers', 'contrib', 'openstack',
437+ 'files')
438+
439+ if not os.path.exists(NAGIOS_PLUGINS):
440+ os.makedirs(NAGIOS_PLUGINS)
441+ for fname in glob.glob(os.path.join(nrpe_files_dir, "check_*")):
442+ if os.path.isfile(fname):
443+ shutil.copy2(fname,
444+ os.path.join(NAGIOS_PLUGINS, os.path.basename(fname)))
445+
446+
447+def add_haproxy_checks(nrpe, unit_name):
448+ """
449+ Add checks for each service in list
450+
451+ :param NRPE nrpe: NRPE object to add check to
452+ :param str unit_name: Unit name to use in check description
453+ """
454+ nrpe.add_check(
455+ shortname='haproxy_servers',
456+ description='Check HAProxy {%s}' % unit_name,
457+ check_cmd='check_haproxy.sh')
458+ nrpe.add_check(
459+ shortname='haproxy_queue',
460+ description='Check HAProxy queue depth {%s}' % unit_name,
461+ check_cmd='check_haproxy_queue_depth.sh')
462
463=== added file 'hooks/charmhelpers/contrib/charmsupport/volumes.py'
464--- hooks/charmhelpers/contrib/charmsupport/volumes.py 1970-01-01 00:00:00 +0000
465+++ hooks/charmhelpers/contrib/charmsupport/volumes.py 2016-05-19 16:36:23 +0000
466@@ -0,0 +1,175 @@
467+# Copyright 2014-2015 Canonical Limited.
468+#
469+# This file is part of charm-helpers.
470+#
471+# charm-helpers is free software: you can redistribute it and/or modify
472+# it under the terms of the GNU Lesser General Public License version 3 as
473+# published by the Free Software Foundation.
474+#
475+# charm-helpers is distributed in the hope that it will be useful,
476+# but WITHOUT ANY WARRANTY; without even the implied warranty of
477+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
478+# GNU Lesser General Public License for more details.
479+#
480+# You should have received a copy of the GNU Lesser General Public License
481+# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
482+
483+'''
484+Functions for managing volumes in juju units. One volume is supported per unit.
485+Subordinates may have their own storage, provided it is on its own partition.
486+
487+Configuration stanzas::
488+
489+ volume-ephemeral:
490+ type: boolean
491+ default: true
492+ description: >
493+ If false, a volume is mounted as sepecified in "volume-map"
494+ If true, ephemeral storage will be used, meaning that log data
495+ will only exist as long as the machine. YOU HAVE BEEN WARNED.
496+ volume-map:
497+ type: string
498+ default: {}
499+ description: >
500+ YAML map of units to device names, e.g:
501+ "{ rsyslog/0: /dev/vdb, rsyslog/1: /dev/vdb }"
502+ Service units will raise a configure-error if volume-ephemeral
503+ is 'true' and no volume-map value is set. Use 'juju set' to set a
504+ value and 'juju resolved' to complete configuration.
505+
506+Usage::
507+
508+ from charmsupport.volumes import configure_volume, VolumeConfigurationError
509+ from charmsupport.hookenv import log, ERROR
510+ def post_mount_hook():
511+ stop_service('myservice')
512+ def post_mount_hook():
513+ start_service('myservice')
514+
515+ if __name__ == '__main__':
516+ try:
517+ configure_volume(before_change=pre_mount_hook,
518+ after_change=post_mount_hook)
519+ except VolumeConfigurationError:
520+ log('Storage could not be configured', ERROR)
521+
522+'''
523+
524+# XXX: Known limitations
525+# - fstab is neither consulted nor updated
526+
527+import os
528+from charmhelpers.core import hookenv
529+from charmhelpers.core import host
530+import yaml
531+
532+
533+MOUNT_BASE = '/srv/juju/volumes'
534+
535+
536+class VolumeConfigurationError(Exception):
537+ '''Volume configuration data is missing or invalid'''
538+ pass
539+
540+
541+def get_config():
542+ '''Gather and sanity-check volume configuration data'''
543+ volume_config = {}
544+ config = hookenv.config()
545+
546+ errors = False
547+
548+ if config.get('volume-ephemeral') in (True, 'True', 'true', 'Yes', 'yes'):
549+ volume_config['ephemeral'] = True
550+ else:
551+ volume_config['ephemeral'] = False
552+
553+ try:
554+ volume_map = yaml.safe_load(config.get('volume-map', '{}'))
555+ except yaml.YAMLError as e:
556+ hookenv.log("Error parsing YAML volume-map: {}".format(e),
557+ hookenv.ERROR)
558+ errors = True
559+ if volume_map is None:
560+ # probably an empty string
561+ volume_map = {}
562+ elif not isinstance(volume_map, dict):
563+ hookenv.log("Volume-map should be a dictionary, not {}".format(
564+ type(volume_map)))
565+ errors = True
566+
567+ volume_config['device'] = volume_map.get(os.environ['JUJU_UNIT_NAME'])
568+ if volume_config['device'] and volume_config['ephemeral']:
569+ # asked for ephemeral storage but also defined a volume ID
570+ hookenv.log('A volume is defined for this unit, but ephemeral '
571+ 'storage was requested', hookenv.ERROR)
572+ errors = True
573+ elif not volume_config['device'] and not volume_config['ephemeral']:
574+ # asked for permanent storage but did not define volume ID
575+ hookenv.log('Ephemeral storage was requested, but there is no volume '
576+ 'defined for this unit.', hookenv.ERROR)
577+ errors = True
578+
579+ unit_mount_name = hookenv.local_unit().replace('/', '-')
580+ volume_config['mountpoint'] = os.path.join(MOUNT_BASE, unit_mount_name)
581+
582+ if errors:
583+ return None
584+ return volume_config
585+
586+
587+def mount_volume(config):
588+ if os.path.exists(config['mountpoint']):
589+ if not os.path.isdir(config['mountpoint']):
590+ hookenv.log('Not a directory: {}'.format(config['mountpoint']))
591+ raise VolumeConfigurationError()
592+ else:
593+ host.mkdir(config['mountpoint'])
594+ if os.path.ismount(config['mountpoint']):
595+ unmount_volume(config)
596+ if not host.mount(config['device'], config['mountpoint'], persist=True):
597+ raise VolumeConfigurationError()
598+
599+
600+def unmount_volume(config):
601+ if os.path.ismount(config['mountpoint']):
602+ if not host.umount(config['mountpoint'], persist=True):
603+ raise VolumeConfigurationError()
604+
605+
606+def managed_mounts():
607+ '''List of all mounted managed volumes'''
608+ return filter(lambda mount: mount[0].startswith(MOUNT_BASE), host.mounts())
609+
610+
611+def configure_volume(before_change=lambda: None, after_change=lambda: None):
612+ '''Set up storage (or don't) according to the charm's volume configuration.
613+ Returns the mount point or "ephemeral". before_change and after_change
614+ are optional functions to be called if the volume configuration changes.
615+ '''
616+
617+ config = get_config()
618+ if not config:
619+ hookenv.log('Failed to read volume configuration', hookenv.CRITICAL)
620+ raise VolumeConfigurationError()
621+
622+ if config['ephemeral']:
623+ if os.path.ismount(config['mountpoint']):
624+ before_change()
625+ unmount_volume(config)
626+ after_change()
627+ return 'ephemeral'
628+ else:
629+ # persistent storage
630+ if os.path.ismount(config['mountpoint']):
631+ mounts = dict(managed_mounts())
632+ if mounts.get(config['mountpoint']) != config['device']:
633+ before_change()
634+ unmount_volume(config)
635+ mount_volume(config)
636+ after_change()
637+ else:
638+ before_change()
639+ mount_volume(config)
640+ after_change()
641+ return config['mountpoint']
642
643=== modified file 'hooks/hooks.py'
644--- hooks/hooks.py 2016-03-30 22:10:17 +0000
645+++ hooks/hooks.py 2016-05-19 16:36:23 +0000
646@@ -26,6 +26,7 @@
647 remote_unit,
648 relation_get,
649 )
650+from charmhelpers.contrib.charmsupport import nrpe
651
652 from charmhelpers.fetch import (
653 apt_install
654@@ -53,7 +54,7 @@
655
656 IMFILE_FILE = '/etc/rsyslog.d/75-rsyslog-imfile.conf'
657 LOGS_TEMPLATE = 'keep_local.template'
658-LOGS_SYSTEM_FILE = '/etc/rsyslog.d/81-local.conf'
659+LOGS_SYSTEM_FILE = '/etc/rsyslog.d/50-default.conf'
660 REPLICATION_FILE = '/etc/rsyslog.d/80-rsyslog-replication.conf'
661
662
663@@ -80,6 +81,18 @@
664 os.remove(LOGS_SYSTEM_FILE)
665
666
667+@hooks.hook("nrpe-external-master-relation-changed")
668+@hooks.hook("local-monitors-relation-changed")
669+def update_nrpe_config():
670+ nrpe_compat = nrpe.NRPE()
671+ nrpe_compat.add_check(
672+ shortname="rsyslog",
673+ description="Check rsyslog is running",
674+ check_cmd="check_procs -c 1: -C rsyslogd"
675+ )
676+ nrpe_compat.write()
677+
678+
679 def update_failover_replication(servers):
680 """
681 Set the configuration file to failover
682@@ -142,7 +155,15 @@
683
684 @hooks.hook()
685 def stop():
686- service_stop("rsyslog")
687+ # Remove any specific logfiles
688+ for conf_file in (IMFILE_FILE, REPLICATION_FILE):
689+ if os.path.exists(conf_file):
690+ os.remove(conf_file)
691+
692+ # Unfortunately rsyslog reconfigure does not restore the default config
693+ # so just put ours in place
694+ update_local_logs(True)
695+ service_restart("rsyslog")
696
697
698 @hooks.hook()
699@@ -199,6 +220,7 @@
700 update_local_logs(config_get("log-locally"))
701 update_imfile(config_get("watch-files").split())
702 update_replication()
703+ update_nrpe_config()
704
705
706 if __name__ == "__main__":
707
708=== added symlink 'hooks/nrpe-external-master-relation-changed'
709=== target is u'hooks.py'
710=== modified file 'metadata.yaml'
711--- metadata.yaml 2015-10-02 17:29:40 +0000
712+++ metadata.yaml 2016-05-19 16:36:23 +0000
713@@ -5,6 +5,10 @@
714 tags: ["system"]
715 description: |
716 Uses rsyslogs facilities to forward to multiple remote syslog servers.
717+provides:
718+ nrpe-external-master:
719+ interface: nrpe-external-master
720+ scope: container
721 requires:
722 juju-info:
723 interface: juju-info
724
725=== modified file 'templates/failover.template'
726--- templates/failover.template 2016-03-23 20:20:05 +0000
727+++ templates/failover.template 2016-05-19 16:36:23 +0000
728@@ -1,5 +1,14 @@
729 #this requires TCP is the protocol to be enabled on rsyslog
730
731+# Explicitly set the tcp protocol to avoid collisions with
732+# other config such as that used by Juju controllers
733+$ActionSendStreamDriver ptcp
734+$ActionSendStreamDriverMode 0
735+
736+$ActionQueueType LinkedList
737+# Drop low priority messages when queue is near to backing up
738+$ActionQueueDiscardMark 9500
739+$ActionQueueDiscardSeverity info
740 *.* @@{{master.private_address}}:514
741
742 $ActionExecOnlyWhenPreviousIsSuspended on
743
744=== modified file 'tests/unit/test_basic.py'
745--- tests/unit/test_basic.py 2016-03-30 21:54:14 +0000
746+++ tests/unit/test_basic.py 2016-05-19 16:36:23 +0000
747@@ -27,7 +27,7 @@
748 "service_restart",
749 "remote_unit",
750 "service_start",
751- "service_stop",
752+ "service_restart",
753 "juju_log",
754 "Server",
755 "update_local_logs",
756@@ -88,10 +88,10 @@
757 self.service_start.assert_called_with("rsyslog")
758
759 def test_stop_charm(self):
760- """Check if start hooks is correctly executed
761+ """Check if rsyslog is returned to default config and restart executed
762 """
763 hooks.hooks.execute(['stop'])
764- self.service_stop.assert_called_with("rsyslog")
765+ self.service_restart.assert_called_with("rsyslog")
766
767 @mock.patch("hooks.hooks.update_replication")
768 def test_syslog_relation_joined(self, replication):

Subscribers

People subscribed via source and target branches