Merge lp:~brad-marshall/charms/trusty/neutron-api/add-nrpe-checks into lp:~openstack-charmers-archive/charms/trusty/neutron-api/trunk

Proposed by Brad Marshall
Status: Superseded
Proposed branch: lp:~brad-marshall/charms/trusty/neutron-api/add-nrpe-checks
Merge into: lp:~openstack-charmers-archive/charms/trusty/neutron-api/trunk
Diff against target: 981 lines (+858/-0)
11 files modified
charm-helpers-sync.yaml (+1/-0)
config.yaml (+10/-0)
files/nrpe-external-master/check_exit_status.pl (+189/-0)
files/nrpe-external-master/check_status_file.py (+60/-0)
files/nrpe-external-master/check_upstart_job (+72/-0)
files/nrpe-external-master/nagios_plugin.py (+78/-0)
hooks/charmhelpers/contrib/charmsupport/nrpe.py (+222/-0)
hooks/charmhelpers/contrib/charmsupport/volumes.py (+156/-0)
hooks/neutron_api_hooks.py (+59/-0)
hooks/neutron_api_utils.py (+8/-0)
metadata.yaml (+3/-0)
To merge this branch: bzr merge lp:~brad-marshall/charms/trusty/neutron-api/add-nrpe-checks
Reviewer Review Type Date Requested Status
Liam Young (community) Needs Fixing
Review via email: mp+241490@code.launchpad.net

This proposal has been superseded by a proposal from 2014-11-17.

Description of the change

Adds nrpe-external-master interface and adds basic nrpe checks.

To post a comment you must log in.
Revision history for this message
Ryan Beisner (1chb1n) wrote :

UOSCI bot says:
charm_lint_check #998 trusty-neutron-api for brad-marshall mp241490
    LINT FAIL: lint-test failed

LINT Results (max last 5 lines):
ERROR:root:Make target returned non-zero.
  hooks/neutron_api_hooks.py:377:80: E501 line too long (92 > 79 characters)
  hooks/neutron_api_hooks.py:398:18: E251 unexpected spaces around keyword / parameter equals
  hooks/neutron_api_hooks.py:398:20: E251 unexpected spaces around keyword / parameter equals
  make: *** [lint] Error 1

Full lint test output: http://paste.ubuntu.com/8955801/
Build: http://10.98.191.181:8080/job/charm_lint_check/998/

Revision history for this message
Ryan Beisner (1chb1n) wrote :

UOSCI bot says:
charm_unit_test #833 trusty-neutron-api for brad-marshall mp241490
    UNIT FAIL: unit-test failed

UNIT Results (max last 5 lines):
  hooks/neutron_api_utils 90 7 92% 215-227
  TOTAL 361 48 87%
  Ran 49 tests in 3.083s
  FAILED (errors=1)
  make: *** [unit_test] Error 1

Full unit test output: http://paste.ubuntu.com/8955810/
Build: http://10.98.191.181:8080/job/charm_unit_test/833/

Revision history for this message
Ryan Beisner (1chb1n) wrote :

UOSCI bot says:
charm_amulet_test #378 trusty-neutron-api for brad-marshall mp241490
    AMULET FAIL: amulet-test missing

AMULET Results (max last 5 lines):
INFO:root:Workspace dir: /var/lib/jenkins/workspace/charm_amulet_test
INFO:root:Reading file: Makefile
INFO:root:Searching for: ['@juju test']
INFO:root:Search string not found in makefile target commands.
ERROR:root:No make target was executed.

Full amulet test output: http://paste.ubuntu.com/8955978/
Build: http://10.98.191.181:8080/job/charm_amulet_test/378/

Revision history for this message
Liam Young (gnuoy) wrote :

Also, could you move the check_upstart_job into charmhelpers as it seems to be common across these mps?

Revision history for this message
Liam Young (gnuoy) wrote :

Other than putting check_upstart_job into charmhelpers this lgtm

review: Approve
Revision history for this message
Liam Young (gnuoy) wrote :

Sorry, that was approved in error. I'd like to wait on the check_upstart_job move

review: Needs Fixing
68. By Brad Marshall

[bradm] Added nrpe checks for sysvinit daemons, change to using services() instead of hard coded daemon list, pep8 fixes.

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

UOSCI bot says:
charm_lint_check #1089 trusty-neutron-api for brad-marshall mp241490
    LINT OK: passed

LINT Results (max last 5 lines):
  I: config.yaml: option ssl_ca has no default value
  I: config.yaml: option ssl_cert has no default value
  I: config.yaml: option os-internal-network has no default value
  I: config.yaml: option os-public-network has no default value
  I: config.yaml: option nsx-tz-uuid has no default value

Full lint test output: http://paste.ubuntu.com/9052160/
Build: http://10.98.191.181:8080/job/charm_lint_check/1089/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

UOSCI bot says:
charm_unit_test #923 trusty-neutron-api for brad-marshall mp241490
    UNIT FAIL: unit-test failed

UNIT Results (max last 5 lines):
  hooks/neutron_api_utils 95 11 88% 177-180, 223-235
  TOTAL 380 65 83%
  Ran 49 tests in 2.920s
  FAILED (errors=1)
  make: *** [unit_test] Error 1

Full unit test output: http://paste.ubuntu.com/9052176/
Build: http://10.98.191.181:8080/job/charm_unit_test/923/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

UOSCI bot says:
charm_amulet_test #431 trusty-neutron-api for brad-marshall mp241490
    AMULET FAIL: amulet-test missing

AMULET Results (max last 5 lines):
INFO:root:Workspace dir: /var/lib/jenkins/workspace/charm_amulet_test
INFO:root:Reading file: Makefile
INFO:root:Searching for: ['@juju test']
INFO:root:Search string not found in makefile target commands.
ERROR:root:No make target was executed.

Full amulet test output: http://paste.ubuntu.com/9052233/
Build: http://10.98.191.181:8080/job/charm_amulet_test/431/

69. By Brad Marshall

[bradm] Removed puppet header from nagios_plugin module

70. By Brad Marshall

[bradm] Removed nagios check files that were moved to nrpe-external-master charm

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charm-helpers-sync.yaml'
2--- charm-helpers-sync.yaml 2014-10-02 09:18:00 +0000
3+++ charm-helpers-sync.yaml 2014-11-17 03:43:00 +0000
4@@ -9,3 +9,4 @@
5 - contrib.storage.linux
6 - payload.execd
7 - contrib.network.ip
8+ - contrib.charmsupport
9
10=== modified file 'config.yaml'
11--- config.yaml 2014-10-14 07:47:50 +0000
12+++ config.yaml 2014-11-17 03:43:00 +0000
13@@ -191,3 +191,13 @@
14 description: |
15 This is uuid of the default NSX L3 Gateway Service.
16 # end of NSX configuration
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-myservice-0
25+ If you're running multiple environments with the same services in them
26+ this allows you to differentiate between them.
27
28=== added directory 'files'
29=== added directory 'files/nrpe-external-master'
30=== added file 'files/nrpe-external-master/check_exit_status.pl'
31--- files/nrpe-external-master/check_exit_status.pl 1970-01-01 00:00:00 +0000
32+++ files/nrpe-external-master/check_exit_status.pl 2014-11-17 03:43:00 +0000
33@@ -0,0 +1,189 @@
34+#!/usr/bin/perl
35+################################################################################
36+# #
37+# Copyright (C) 2011 Chad Columbus <ccolumbu@hotmail.com> #
38+# #
39+# This program is free software; you can redistribute it and/or modify #
40+# it under the terms of the GNU General Public License as published by #
41+# the Free Software Foundation; either version 2 of the License, or #
42+# (at your option) any later version. #
43+# #
44+# This program is distributed in the hope that it will be useful, #
45+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
46+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
47+# GNU General Public License for more details. #
48+# #
49+# You should have received a copy of the GNU General Public License #
50+# along with this program; if not, write to the Free Software #
51+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #
52+# #
53+################################################################################
54+
55+use strict;
56+use Getopt::Std;
57+$| = 1;
58+
59+my %opts;
60+getopts('heronp:s:', \%opts);
61+
62+my $VERSION = "Version 1.0";
63+my $AUTHOR = '(c) 2011 Chad Columbus <ccolumbu@hotmail.com>';
64+
65+# Default values:
66+my $script_to_check;
67+my $pattern = 'is running';
68+my $cmd;
69+my $message;
70+my $error;
71+
72+# Exit codes
73+my $STATE_OK = 0;
74+my $STATE_WARNING = 1;
75+my $STATE_CRITICAL = 2;
76+my $STATE_UNKNOWN = 3;
77+
78+# Parse command line options
79+if ($opts{'h'} || scalar(%opts) == 0) {
80+ &print_help();
81+ exit($STATE_OK);
82+}
83+
84+# Make sure scipt is provided:
85+if ($opts{'s'} eq '') {
86+ # Script to run not provided
87+ print "\nYou must provide a script to run. Example: -s /etc/init.d/httpd\n";
88+ exit($STATE_UNKNOWN);
89+} else {
90+ $script_to_check = $opts{'s'};
91+}
92+
93+# Make sure only a-z, 0-9, /, _, and - are used in the script.
94+if ($script_to_check =~ /[^a-z0-9\_\-\/\.]/) {
95+ # Script contains illegal characters exit.
96+ print "\nScript to check can only contain Letters, Numbers, Periods, Underscores, Hyphens, and/or Slashes\n";
97+ exit($STATE_UNKNOWN);
98+}
99+
100+# See if script is executable
101+if (! -x "$script_to_check") {
102+ print "\nIt appears you can't execute $script_to_check, $!\n";
103+ exit($STATE_UNKNOWN);
104+}
105+
106+# If a pattern is provided use it:
107+if ($opts{'p'} ne '') {
108+ $pattern = $opts{'p'};
109+}
110+
111+# If -r run command via sudo as root:
112+if ($opts{'r'}) {
113+ $cmd = "sudo -n $script_to_check status" . ' 2>&1';
114+} else {
115+ $cmd = "$script_to_check status" . ' 2>&1';
116+}
117+
118+my $cmd_result = `$cmd`;
119+chomp($cmd_result);
120+if ($cmd_result =~ /sudo/i) {
121+ # This means it could not run the sudo command
122+ $message = "$script_to_check CRITICAL - Could not run: 'sudo -n $script_to_check status'. Result is $cmd_result";
123+ $error = $STATE_UNKNOWN;
124+} else {
125+ # Check exitstatus instead of output:
126+ if ($opts{'e'} == 1) {
127+ if ($? != 0) {
128+ # error
129+ $message = "$script_to_check CRITICAL - Exit code: $?\.";
130+ if ($opts{'o'} == 0) {
131+ $message .= " $cmd_result";
132+ }
133+ $error = $STATE_CRITICAL;
134+ } else {
135+ # success
136+ $message = "$script_to_check OK - Exit code: $?\.";
137+ if ($opts{'o'} == 0) {
138+ $message .= " $cmd_result";
139+ }
140+ $error = $STATE_OK;
141+ }
142+ } else {
143+ my $not_check = 1;
144+ if ($opts{'n'} == 1) {
145+ $not_check = 0;
146+ }
147+ if (($cmd_result =~ /$pattern/i) == $not_check) {
148+ $message = "$script_to_check OK";
149+ if ($opts{'o'} == 0) {
150+ $message .= " - $cmd_result";
151+ }
152+ $error = $STATE_OK;
153+ } else {
154+ $message = "$script_to_check CRITICAL";
155+ if ($opts{'o'} == 0) {
156+ $message .= " - $cmd_result";
157+ }
158+ $error = $STATE_CRITICAL;
159+ }
160+ }
161+}
162+
163+if ($message eq '') {
164+ print "Error: program failed in an unknown way\n";
165+ exit($STATE_UNKNOWN);
166+}
167+
168+if ($error) {
169+ print "$message\n";
170+ exit($error);
171+} else {
172+ # If we get here we are OK
173+ print "$message\n";
174+ exit($STATE_OK);
175+}
176+
177+####################################
178+# Start Subs:
179+####################################
180+sub print_help() {
181+ print << "EOF";
182+Check the output or exit status of a script.
183+$VERSION
184+$AUTHOR
185+
186+Options:
187+-h
188+ Print detailed help screen
189+
190+-s
191+ 'FULL PATH TO SCRIPT' (required)
192+ This is the script to run, the script is designed to run scripts in the
193+ /etc/init.d dir (but can run any script) and will call the script with
194+ a 'status' argument. So if you use another script make sure it will
195+ work with /path/script status, example: /etc/init.d/httpd status
196+
197+-e
198+ This is the "exitstaus" flag, it means check the exit status
199+ code instead of looking for a pattern in the output of the script.
200+
201+-p 'REGEX'
202+ This is a pattern to look for in the output of the script to confirm it
203+ is running, default is 'is running', but not all init.d scripts output
204+ (iptables), so you can specify an arbitrary pattern.
205+ All patterns are case insensitive.
206+
207+-n
208+ This is the "NOT" flag, it means not the -p pattern, so if you want to
209+ make sure the output of the script does NOT contain -p 'REGEX'
210+
211+-r
212+ This is the "ROOT" flag, it means run as root via sudo. You will need a
213+ line in your /etc/sudoers file like:
214+ nagios ALL=(root) NOPASSWD: /etc/init.d/* status
215+
216+-o
217+ This is the "SUPPRESS OUTPUT" flag. Some programs have a long output
218+ (like iptables), this flag suppresses that output so it is not printed
219+ as a part of the nagios message.
220+EOF
221+}
222+
223
224=== added file 'files/nrpe-external-master/check_status_file.py'
225--- files/nrpe-external-master/check_status_file.py 1970-01-01 00:00:00 +0000
226+++ files/nrpe-external-master/check_status_file.py 2014-11-17 03:43:00 +0000
227@@ -0,0 +1,60 @@
228+#!/usr/bin/python
229+
230+# m
231+# mmmm m m mmmm mmmm mmm mm#mm
232+# #" "# # # #" "# #" "# #" # #
233+# # # # # # # # # #"""" #
234+# ##m#" "mm"# ##m#" ##m#" "#mm" "mm
235+# # # #
236+# " " "
237+# This file is managed by puppet. Do not make local changes.
238+
239+#
240+# Copyright 2014 Canonical Ltd.
241+#
242+# Author: Jacek Nykis <jacek.nykis@canonical.com>
243+#
244+
245+import re
246+import nagios_plugin
247+
248+
249+def parse_args():
250+ import argparse
251+
252+ parser = argparse.ArgumentParser(
253+ description='Read file and return nagios status based on its content',
254+ formatter_class=argparse.ArgumentDefaultsHelpFormatter)
255+ parser.add_argument('-f', '--status-file', required=True,
256+ help='Status file path')
257+ parser.add_argument('-c', '--critical-text', default='CRITICAL',
258+ help='String indicating critical status')
259+ parser.add_argument('-w', '--warning-text', default='WARNING',
260+ help='String indicating warning status')
261+ parser.add_argument('-o', '--ok-text', default='OK',
262+ help='String indicating OK status')
263+ parser.add_argument('-u', '--unknown-text', default='UNKNOWN',
264+ help='String indicating unknown status')
265+ return parser.parse_args()
266+
267+
268+def check_status(args):
269+ nagios_plugin.check_file_freshness(args.status_file, 43200)
270+
271+ with open(args.status_file, "r") as f:
272+ content = [l.strip() for l in f.readlines()]
273+
274+ for line in content:
275+ if re.search(args.critical_text, line):
276+ raise nagios_plugin.CriticalError(line)
277+ elif re.search(args.warning_text, line):
278+ raise nagios_plugin.WarnError(line)
279+ elif re.search(args.unknown_text, line):
280+ raise nagios_plugin.UnknownError(line)
281+ else:
282+ print line
283+
284+
285+if __name__ == '__main__':
286+ args = parse_args()
287+ nagios_plugin.try_check(check_status, args)
288
289=== added file 'files/nrpe-external-master/check_upstart_job'
290--- files/nrpe-external-master/check_upstart_job 1970-01-01 00:00:00 +0000
291+++ files/nrpe-external-master/check_upstart_job 2014-11-17 03:43:00 +0000
292@@ -0,0 +1,72 @@
293+#!/usr/bin/python
294+
295+#
296+# Copyright 2012, 2013 Canonical Ltd.
297+#
298+# Author: Paul Collins <paul.collins@canonical.com>
299+#
300+# Based on http://www.eurion.net/python-snippets/snippet/Upstart%20service%20status.html
301+#
302+
303+import sys
304+
305+import dbus
306+
307+
308+class Upstart(object):
309+ def __init__(self):
310+ self._bus = dbus.SystemBus()
311+ self._upstart = self._bus.get_object('com.ubuntu.Upstart',
312+ '/com/ubuntu/Upstart')
313+ def get_job(self, job_name):
314+ path = self._upstart.GetJobByName(job_name,
315+ dbus_interface='com.ubuntu.Upstart0_6')
316+ return self._bus.get_object('com.ubuntu.Upstart', path)
317+
318+ def get_properties(self, job):
319+ path = job.GetInstance([], dbus_interface='com.ubuntu.Upstart0_6.Job')
320+ instance = self._bus.get_object('com.ubuntu.Upstart', path)
321+ return instance.GetAll('com.ubuntu.Upstart0_6.Instance',
322+ dbus_interface=dbus.PROPERTIES_IFACE)
323+
324+ def get_job_instances(self, job_name):
325+ job = self.get_job(job_name)
326+ paths = job.GetAllInstances([], dbus_interface='com.ubuntu.Upstart0_6.Job')
327+ return [self._bus.get_object('com.ubuntu.Upstart', path) for path in paths]
328+
329+ def get_job_instance_properties(self, job):
330+ return job.GetAll('com.ubuntu.Upstart0_6.Instance',
331+ dbus_interface=dbus.PROPERTIES_IFACE)
332+
333+try:
334+ upstart = Upstart()
335+ try:
336+ job = upstart.get_job(sys.argv[1])
337+ props = upstart.get_properties(job)
338+
339+ if props['state'] == 'running':
340+ print 'OK: %s is running' % sys.argv[1]
341+ sys.exit(0)
342+ else:
343+ print 'CRITICAL: %s is not running' % sys.argv[1]
344+ sys.exit(2)
345+
346+ except dbus.DBusException as e:
347+ instances = upstart.get_job_instances(sys.argv[1])
348+ propses = [upstart.get_job_instance_properties(instance) for instance in instances]
349+ states = dict([(props['name'], props['state']) for props in propses])
350+ if len(states) != states.values().count('running'):
351+ not_running = []
352+ for name in states.keys():
353+ if states[name] != 'running':
354+ not_running.append(name)
355+ print 'CRITICAL: %d instances of %s not running: %s' % \
356+ (len(not_running), sys.argv[1], not_running.join(', '))
357+ sys.exit(2)
358+ else:
359+ print 'OK: %d instances of %s running' % (len(states), sys.argv[1])
360+
361+except dbus.DBusException as e:
362+ print 'CRITICAL: failed to get properties of \'%s\' from upstart' % sys.argv[1]
363+ sys.exit(2)
364+
365
366=== added file 'files/nrpe-external-master/nagios_plugin.py'
367--- files/nrpe-external-master/nagios_plugin.py 1970-01-01 00:00:00 +0000
368+++ files/nrpe-external-master/nagios_plugin.py 2014-11-17 03:43:00 +0000
369@@ -0,0 +1,78 @@
370+#!/usr/bin/env python
371+# m
372+# mmmm m m mmmm mmmm mmm mm#mm
373+# #" "# # # #" "# #" "# #" # #
374+# # # # # # # # # #"""" #
375+# ##m#" "mm"# ##m#" ##m#" "#mm" "mm
376+# # # #
377+# " " "
378+# This file is managed by puppet. Do not make local changes.
379+
380+# Copyright (C) 2005, 2006, 2007, 2012 James Troup <james.troup@canonical.com>
381+
382+import os
383+import stat
384+import time
385+import traceback
386+import sys
387+
388+
389+################################################################################
390+
391+class CriticalError(Exception):
392+ """This indicates a critical error."""
393+ pass
394+
395+
396+class WarnError(Exception):
397+ """This indicates a warning condition."""
398+ pass
399+
400+
401+class UnknownError(Exception):
402+ """This indicates a unknown error was encountered."""
403+ pass
404+
405+
406+def try_check(function, *args, **kwargs):
407+ """Perform a check with error/warn/unknown handling."""
408+ try:
409+ function(*args, **kwargs)
410+ except UnknownError, msg:
411+ print msg
412+ sys.exit(3)
413+ except CriticalError, msg:
414+ print msg
415+ sys.exit(2)
416+ except WarnError, msg:
417+ print msg
418+ sys.exit(1)
419+ except:
420+ print "%s raised unknown exception '%s'" % (function, sys.exc_info()[0])
421+ print '=' * 60
422+ traceback.print_exc(file=sys.stdout)
423+ print '=' * 60
424+ sys.exit(3)
425+
426+
427+################################################################################
428+
429+def check_file_freshness(filename, newer_than=600):
430+ """Check a file exists, is readable and is newer than <n> seconds (where <n> defaults to 600)."""
431+ # First check the file exists and is readable
432+ if not os.path.exists(filename):
433+ raise CriticalError("%s: does not exist." % (filename))
434+ if os.access(filename, os.R_OK) == 0:
435+ raise CriticalError("%s: is not readable." % (filename))
436+
437+ # Then ensure the file is up-to-date enough
438+ mtime = os.stat(filename)[stat.ST_MTIME]
439+ last_modified = time.time() - mtime
440+ if last_modified > newer_than:
441+ raise CriticalError("%s: was last modified on %s and is too old (> %s seconds)."
442+ % (filename, time.ctime(mtime), newer_than))
443+ if last_modified < 0:
444+ raise CriticalError("%s: was last modified on %s which is in the future."
445+ % (filename, time.ctime(mtime)))
446+
447+################################################################################
448
449=== added directory 'hooks/charmhelpers/contrib/charmsupport'
450=== added file 'hooks/charmhelpers/contrib/charmsupport/__init__.py'
451=== added file 'hooks/charmhelpers/contrib/charmsupport/nrpe.py'
452--- hooks/charmhelpers/contrib/charmsupport/nrpe.py 1970-01-01 00:00:00 +0000
453+++ hooks/charmhelpers/contrib/charmsupport/nrpe.py 2014-11-17 03:43:00 +0000
454@@ -0,0 +1,222 @@
455+"""Compatibility with the nrpe-external-master charm"""
456+# Copyright 2012 Canonical Ltd.
457+#
458+# Authors:
459+# Matthew Wedgwood <matthew.wedgwood@canonical.com>
460+
461+import subprocess
462+import pwd
463+import grp
464+import os
465+import re
466+import shlex
467+import yaml
468+
469+from charmhelpers.core.hookenv import (
470+ config,
471+ local_unit,
472+ log,
473+ relation_ids,
474+ relation_set,
475+)
476+
477+from charmhelpers.core.host import service
478+
479+# This module adds compatibility with the nrpe-external-master and plain nrpe
480+# subordinate charms. To use it in your charm:
481+#
482+# 1. Update metadata.yaml
483+#
484+# provides:
485+# (...)
486+# nrpe-external-master:
487+# interface: nrpe-external-master
488+# scope: container
489+#
490+# and/or
491+#
492+# provides:
493+# (...)
494+# local-monitors:
495+# interface: local-monitors
496+# scope: container
497+
498+#
499+# 2. Add the following to config.yaml
500+#
501+# nagios_context:
502+# default: "juju"
503+# type: string
504+# description: |
505+# Used by the nrpe subordinate charms.
506+# A string that will be prepended to instance name to set the host name
507+# in nagios. So for instance the hostname would be something like:
508+# juju-myservice-0
509+# If you're running multiple environments with the same services in them
510+# this allows you to differentiate between them.
511+#
512+# 3. Add custom checks (Nagios plugins) to files/nrpe-external-master
513+#
514+# 4. Update your hooks.py with something like this:
515+#
516+# from charmsupport.nrpe import NRPE
517+# (...)
518+# def update_nrpe_config():
519+# nrpe_compat = NRPE()
520+# nrpe_compat.add_check(
521+# shortname = "myservice",
522+# description = "Check MyService",
523+# check_cmd = "check_http -w 2 -c 10 http://localhost"
524+# )
525+# nrpe_compat.add_check(
526+# "myservice_other",
527+# "Check for widget failures",
528+# check_cmd = "/srv/myapp/scripts/widget_check"
529+# )
530+# nrpe_compat.write()
531+#
532+# def config_changed():
533+# (...)
534+# update_nrpe_config()
535+#
536+# def nrpe_external_master_relation_changed():
537+# update_nrpe_config()
538+#
539+# def local_monitors_relation_changed():
540+# update_nrpe_config()
541+#
542+# 5. ln -s hooks.py nrpe-external-master-relation-changed
543+# ln -s hooks.py local-monitors-relation-changed
544+
545+
546+class CheckException(Exception):
547+ pass
548+
549+
550+class Check(object):
551+ shortname_re = '[A-Za-z0-9-_]+$'
552+ service_template = ("""
553+#---------------------------------------------------
554+# This file is Juju managed
555+#---------------------------------------------------
556+define service {{
557+ use active-service
558+ host_name {nagios_hostname}
559+ service_description {nagios_hostname}[{shortname}] """
560+ """{description}
561+ check_command check_nrpe!{command}
562+ servicegroups {nagios_servicegroup}
563+}}
564+""")
565+
566+ def __init__(self, shortname, description, check_cmd):
567+ super(Check, self).__init__()
568+ # XXX: could be better to calculate this from the service name
569+ if not re.match(self.shortname_re, shortname):
570+ raise CheckException("shortname must match {}".format(
571+ Check.shortname_re))
572+ self.shortname = shortname
573+ self.command = "check_{}".format(shortname)
574+ # Note: a set of invalid characters is defined by the
575+ # Nagios server config
576+ # The default is: illegal_object_name_chars=`~!$%^&*"|'<>?,()=
577+ self.description = description
578+ self.check_cmd = self._locate_cmd(check_cmd)
579+
580+ def _locate_cmd(self, check_cmd):
581+ search_path = (
582+ '/',
583+ os.path.join(os.environ['CHARM_DIR'],
584+ 'files/nrpe-external-master'),
585+ '/usr/lib/nagios/plugins',
586+ '/usr/local/lib/nagios/plugins',
587+ )
588+ parts = shlex.split(check_cmd)
589+ for path in search_path:
590+ if os.path.exists(os.path.join(path, parts[0])):
591+ command = os.path.join(path, parts[0])
592+ if len(parts) > 1:
593+ command += " " + " ".join(parts[1:])
594+ return command
595+ log('Check command not found: {}'.format(parts[0]))
596+ return ''
597+
598+ def write(self, nagios_context, hostname):
599+ nrpe_check_file = '/etc/nagios/nrpe.d/{}.cfg'.format(
600+ self.command)
601+ with open(nrpe_check_file, 'w') as nrpe_check_config:
602+ nrpe_check_config.write("# check {}\n".format(self.shortname))
603+ nrpe_check_config.write("command[{}]={}\n".format(
604+ self.command, self.check_cmd))
605+
606+ if not os.path.exists(NRPE.nagios_exportdir):
607+ log('Not writing service config as {} is not accessible'.format(
608+ NRPE.nagios_exportdir))
609+ else:
610+ self.write_service_config(nagios_context, hostname)
611+
612+ def write_service_config(self, nagios_context, hostname):
613+ for f in os.listdir(NRPE.nagios_exportdir):
614+ if re.search('.*{}.cfg'.format(self.command), f):
615+ os.remove(os.path.join(NRPE.nagios_exportdir, f))
616+
617+ templ_vars = {
618+ 'nagios_hostname': hostname,
619+ 'nagios_servicegroup': nagios_context,
620+ 'description': self.description,
621+ 'shortname': self.shortname,
622+ 'command': self.command,
623+ }
624+ nrpe_service_text = Check.service_template.format(**templ_vars)
625+ nrpe_service_file = '{}/service__{}_{}.cfg'.format(
626+ NRPE.nagios_exportdir, hostname, self.command)
627+ with open(nrpe_service_file, 'w') as nrpe_service_config:
628+ nrpe_service_config.write(str(nrpe_service_text))
629+
630+ def run(self):
631+ subprocess.call(self.check_cmd)
632+
633+
634+class NRPE(object):
635+ nagios_logdir = '/var/log/nagios'
636+ nagios_exportdir = '/var/lib/nagios/export'
637+ nrpe_confdir = '/etc/nagios/nrpe.d'
638+
639+ def __init__(self, hostname=None):
640+ super(NRPE, self).__init__()
641+ self.config = config()
642+ self.nagios_context = self.config['nagios_context']
643+ self.unit_name = local_unit().replace('/', '-')
644+ if hostname:
645+ self.hostname = hostname
646+ else:
647+ self.hostname = "{}-{}".format(self.nagios_context, self.unit_name)
648+ self.checks = []
649+
650+ def add_check(self, *args, **kwargs):
651+ self.checks.append(Check(*args, **kwargs))
652+
653+ def write(self):
654+ try:
655+ nagios_uid = pwd.getpwnam('nagios').pw_uid
656+ nagios_gid = grp.getgrnam('nagios').gr_gid
657+ except:
658+ log("Nagios user not set up, nrpe checks not updated")
659+ return
660+
661+ if not os.path.exists(NRPE.nagios_logdir):
662+ os.mkdir(NRPE.nagios_logdir)
663+ os.chown(NRPE.nagios_logdir, nagios_uid, nagios_gid)
664+
665+ nrpe_monitors = {}
666+ monitors = {"monitors": {"remote": {"nrpe": nrpe_monitors}}}
667+ for nrpecheck in self.checks:
668+ nrpecheck.write(self.nagios_context, self.hostname)
669+ nrpe_monitors[nrpecheck.shortname] = {
670+ "command": nrpecheck.command,
671+ }
672+
673+ service('restart', 'nagios-nrpe-server')
674+
675+ for rid in relation_ids("local-monitors"):
676+ relation_set(relation_id=rid, monitors=yaml.dump(monitors))
677
678=== added file 'hooks/charmhelpers/contrib/charmsupport/volumes.py'
679--- hooks/charmhelpers/contrib/charmsupport/volumes.py 1970-01-01 00:00:00 +0000
680+++ hooks/charmhelpers/contrib/charmsupport/volumes.py 2014-11-17 03:43:00 +0000
681@@ -0,0 +1,156 @@
682+'''
683+Functions for managing volumes in juju units. One volume is supported per unit.
684+Subordinates may have their own storage, provided it is on its own partition.
685+
686+Configuration stanzas:
687+ volume-ephemeral:
688+ type: boolean
689+ default: true
690+ description: >
691+ If false, a volume is mounted as sepecified in "volume-map"
692+ If true, ephemeral storage will be used, meaning that log data
693+ will only exist as long as the machine. YOU HAVE BEEN WARNED.
694+ volume-map:
695+ type: string
696+ default: {}
697+ description: >
698+ YAML map of units to device names, e.g:
699+ "{ rsyslog/0: /dev/vdb, rsyslog/1: /dev/vdb }"
700+ Service units will raise a configure-error if volume-ephemeral
701+ is 'true' and no volume-map value is set. Use 'juju set' to set a
702+ value and 'juju resolved' to complete configuration.
703+
704+Usage:
705+ from charmsupport.volumes import configure_volume, VolumeConfigurationError
706+ from charmsupport.hookenv import log, ERROR
707+ def post_mount_hook():
708+ stop_service('myservice')
709+ def post_mount_hook():
710+ start_service('myservice')
711+
712+ if __name__ == '__main__':
713+ try:
714+ configure_volume(before_change=pre_mount_hook,
715+ after_change=post_mount_hook)
716+ except VolumeConfigurationError:
717+ log('Storage could not be configured', ERROR)
718+'''
719+
720+# XXX: Known limitations
721+# - fstab is neither consulted nor updated
722+
723+import os
724+from charmhelpers.core import hookenv
725+from charmhelpers.core import host
726+import yaml
727+
728+
729+MOUNT_BASE = '/srv/juju/volumes'
730+
731+
732+class VolumeConfigurationError(Exception):
733+ '''Volume configuration data is missing or invalid'''
734+ pass
735+
736+
737+def get_config():
738+ '''Gather and sanity-check volume configuration data'''
739+ volume_config = {}
740+ config = hookenv.config()
741+
742+ errors = False
743+
744+ if config.get('volume-ephemeral') in (True, 'True', 'true', 'Yes', 'yes'):
745+ volume_config['ephemeral'] = True
746+ else:
747+ volume_config['ephemeral'] = False
748+
749+ try:
750+ volume_map = yaml.safe_load(config.get('volume-map', '{}'))
751+ except yaml.YAMLError as e:
752+ hookenv.log("Error parsing YAML volume-map: {}".format(e),
753+ hookenv.ERROR)
754+ errors = True
755+ if volume_map is None:
756+ # probably an empty string
757+ volume_map = {}
758+ elif not isinstance(volume_map, dict):
759+ hookenv.log("Volume-map should be a dictionary, not {}".format(
760+ type(volume_map)))
761+ errors = True
762+
763+ volume_config['device'] = volume_map.get(os.environ['JUJU_UNIT_NAME'])
764+ if volume_config['device'] and volume_config['ephemeral']:
765+ # asked for ephemeral storage but also defined a volume ID
766+ hookenv.log('A volume is defined for this unit, but ephemeral '
767+ 'storage was requested', hookenv.ERROR)
768+ errors = True
769+ elif not volume_config['device'] and not volume_config['ephemeral']:
770+ # asked for permanent storage but did not define volume ID
771+ hookenv.log('Ephemeral storage was requested, but there is no volume '
772+ 'defined for this unit.', hookenv.ERROR)
773+ errors = True
774+
775+ unit_mount_name = hookenv.local_unit().replace('/', '-')
776+ volume_config['mountpoint'] = os.path.join(MOUNT_BASE, unit_mount_name)
777+
778+ if errors:
779+ return None
780+ return volume_config
781+
782+
783+def mount_volume(config):
784+ if os.path.exists(config['mountpoint']):
785+ if not os.path.isdir(config['mountpoint']):
786+ hookenv.log('Not a directory: {}'.format(config['mountpoint']))
787+ raise VolumeConfigurationError()
788+ else:
789+ host.mkdir(config['mountpoint'])
790+ if os.path.ismount(config['mountpoint']):
791+ unmount_volume(config)
792+ if not host.mount(config['device'], config['mountpoint'], persist=True):
793+ raise VolumeConfigurationError()
794+
795+
796+def unmount_volume(config):
797+ if os.path.ismount(config['mountpoint']):
798+ if not host.umount(config['mountpoint'], persist=True):
799+ raise VolumeConfigurationError()
800+
801+
802+def managed_mounts():
803+ '''List of all mounted managed volumes'''
804+ return filter(lambda mount: mount[0].startswith(MOUNT_BASE), host.mounts())
805+
806+
807+def configure_volume(before_change=lambda: None, after_change=lambda: None):
808+ '''Set up storage (or don't) according to the charm's volume configuration.
809+ Returns the mount point or "ephemeral". before_change and after_change
810+ are optional functions to be called if the volume configuration changes.
811+ '''
812+
813+ config = get_config()
814+ if not config:
815+ hookenv.log('Failed to read volume configuration', hookenv.CRITICAL)
816+ raise VolumeConfigurationError()
817+
818+ if config['ephemeral']:
819+ if os.path.ismount(config['mountpoint']):
820+ before_change()
821+ unmount_volume(config)
822+ after_change()
823+ return 'ephemeral'
824+ else:
825+ # persistent storage
826+ if os.path.ismount(config['mountpoint']):
827+ mounts = dict(managed_mounts())
828+ if mounts.get(config['mountpoint']) != config['device']:
829+ before_change()
830+ unmount_volume(config)
831+ mount_volume(config)
832+ after_change()
833+ else:
834+ before_change()
835+ mount_volume(config)
836+ after_change()
837+ return config['mountpoint']
838
839=== modified file 'hooks/neutron_api_hooks.py'
840--- hooks/neutron_api_hooks.py 2014-10-21 13:07:03 +0000
841+++ hooks/neutron_api_hooks.py 2014-11-17 03:43:00 +0000
842@@ -1,6 +1,7 @@
843 #!/usr/bin/python
844
845 import sys
846+import os
847 import uuid
848
849 from subprocess import check_call
850@@ -14,8 +15,10 @@
851 relation_get,
852 relation_ids,
853 relation_set,
854+ relations_of_type,
855 open_port,
856 unit_get,
857+ local_unit,
858 )
859
860 from charmhelpers.core.host import (
861@@ -45,6 +48,7 @@
862 do_openstack_upgrade,
863 register_configs,
864 restart_map,
865+ services,
866 setup_ipv6
867 )
868 from neutron_api_context import (
869@@ -73,6 +77,8 @@
870
871 from charmhelpers.contrib.openstack.context import ADDRESS_TYPES
872
873+from charmhelpers.contrib.charmsupport.nrpe import NRPE
874+
875 hooks = Hooks()
876 CONFIGS = register_configs()
877
878@@ -120,6 +126,7 @@
879 if openstack_upgrade_available('neutron-server'):
880 do_openstack_upgrade(CONFIGS)
881 configure_https()
882+ update_nrpe_config()
883 CONFIGS.write_all()
884 for r_id in relation_ids('neutron-api'):
885 neutron_api_relation_joined(rid=r_id)
886@@ -369,6 +376,58 @@
887 neutron_api_relation_joined(rid=rid)
888
889
890+@hooks.hook('nrpe-external-master-relation-joined',
891+ 'nrpe-external-master-relation-changed')
892+def update_nrpe_config():
893+ # Find out if nrpe set nagios_hostname
894+ hostname = None
895+ host_context = None
896+ for rel in relations_of_type('nrpe-external-master'):
897+ if 'nagios_hostname' in rel:
898+ hostname = rel['nagios_hostname']
899+ host_context = rel['nagios_host_context']
900+ break
901+ nrpe = NRPE(hostname=hostname)
902+ apt_install('python-dbus')
903+
904+ if host_context:
905+ current_unit = "%s:%s" % (host_context, local_unit())
906+ else:
907+ current_unit = local_unit()
908+
909+ services_to_monitor = services()
910+
911+ for service in services_to_monitor:
912+ upstart_init = '/etc/init/%s.conf' % service
913+ sysv_init = '/etc/init.d/%s' % service
914+
915+ if os.path.exists(upstart_init):
916+ nrpe.add_check(
917+ shortname=service,
918+ description='process check {%s}' % current_unit,
919+ check_cmd='check_upstart_job %s' % service,
920+ )
921+ elif os.path.exists(sysv_init):
922+ cronpath = '/etc/cron.d/nagios-service-check-%s' % service
923+ checkpath = os.path.join(os.environ['CHARM_DIR'],
924+ 'files/nrpe-external-master',
925+ 'check_exit_status.pl'),
926+ cron_template = '*/5 * * * * root %s -s \
927+/etc/init.d/%s status > /var/lib/nagios/service-check-%s.txt\n' \
928+ % (checkpath[0], service, service)
929+ f = open(cronpath, 'w')
930+ f.write(cron_template)
931+ f.close()
932+ nrpe.add_check(
933+ shortname=service,
934+ description='process check {%s}' % current_unit,
935+ check_cmd='check_status_file.py -f \
936+/var/lib/nagios/service-check-%s.txt' % service,
937+ )
938+
939+ nrpe.write()
940+
941+
942 def main():
943 try:
944 hooks.execute(sys.argv)
945
946=== modified file 'hooks/neutron_api_utils.py'
947--- hooks/neutron_api_utils.py 2014-10-21 13:07:03 +0000
948+++ hooks/neutron_api_utils.py 2014-11-17 03:43:00 +0000
949@@ -172,6 +172,14 @@
950 if v['services']])
951
952
953+def services():
954+ ''' Returns a list of services associate with this charm '''
955+ _services = []
956+ for v in restart_map().values():
957+ _services = _services + v
958+ return list(set(_services))
959+
960+
961 def keystone_ca_cert_b64():
962 '''Returns the local Keystone-provided CA cert if it exists, or None.'''
963 if not os.path.isfile(CA_CERT_PATH):
964
965=== added symlink 'hooks/nrpe-external-master-relation-changed'
966=== target is u'neutron_api_hooks.py'
967=== added symlink 'hooks/nrpe-external-master-relation-joined'
968=== target is u'neutron_api_hooks.py'
969=== modified file 'metadata.yaml'
970--- metadata.yaml 2014-06-24 10:59:40 +0000
971+++ metadata.yaml 2014-11-17 03:43:00 +0000
972@@ -15,6 +15,9 @@
973 categories:
974 - openstack
975 provides:
976+ nrpe-external-master:
977+ interface: nrpe-external-master
978+ scope: container
979 neutron-api:
980 interface: neutron-api
981 neutron-plugin-api:

Subscribers

People subscribed via source and target branches