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