Merge lp:~brad-marshall/charms/trusty/neutron-api/add-nrpe-checks into lp:~openstack-charmers-archive/charms/trusty/neutron-api/trunk
- Trusty Tahr (14.04)
- add-nrpe-checks
- Merge into trunk
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 | ||||
Related bugs: |
|
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.
Commit message
Description of the change
Adds nrpe-external-
Ryan Beisner (1chb1n) wrote : | # |
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/
TOTAL 361 48 87%
Ran 49 tests in 3.083s
FAILED (errors=1)
make: *** [unit_test] Error 1
Full unit test output: http://
Build: http://
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/
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://
Build: http://
Liam Young (gnuoy) wrote : | # |
Also, could you move the check_upstart_job into charmhelpers as it seems to be common across these mps?
Liam Young (gnuoy) wrote : | # |
Other than putting check_upstart_job into charmhelpers this lgtm
Liam Young (gnuoy) wrote : | # |
Sorry, that was approved in error. I'd like to wait on the check_upstart_job move
- 68. By Brad Marshall
-
[bradm] Added nrpe checks for sysvinit daemons, change to using services() instead of hard coded daemon list, pep8 fixes.
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://
Build: http://
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/
TOTAL 380 65 83%
Ran 49 tests in 2.920s
FAILED (errors=1)
make: *** [unit_test] Error 1
Full unit test output: http://
Build: http://
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/
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://
Build: http://
- 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
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: |
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): neutron_ api_hooks. py:377: 80: E501 line too long (92 > 79 characters) neutron_ api_hooks. py:398: 18: E251 unexpected spaces around keyword / parameter equals neutron_ api_hooks. py:398: 20: E251 unexpected spaces around keyword / parameter equals
ERROR:root:Make target returned non-zero.
hooks/
hooks/
hooks/
make: *** [lint] Error 1
Full lint test output: http:// paste.ubuntu. com/8955801/ 10.98.191. 181:8080/ job/charm_ lint_check/ 998/
Build: http://