Merge lp:~sinzui/charms/precise/juju-gui/unstable-nagios into lp:~juju-gui/charms/precise/juju-gui/trunk

Proposed by Curtis Hovey
Status: Merged
Merged at revision: 90
Proposed branch: lp:~sinzui/charms/precise/juju-gui/unstable-nagios
Merge into: lp:~juju-gui/charms/precise/juju-gui/trunk
Diff against target: 444 lines (+377/-2)
8 files modified
config.yaml (+11/-1)
files/nrpe-external-master/check-app-access.sh (+18/-0)
metadata.yaml (+3/-0)
revision (+1/-1)
scripts/charmsupport/hookenv.py (+150/-0)
scripts/charmsupport/nrpe.py (+169/-0)
scripts/update-nrpe.py (+14/-0)
tests/20-functional.test (+11/-0)
To merge this branch: bzr merge lp:~sinzui/charms/precise/juju-gui/unstable-nagios
Reviewer Review Type Date Requested Status
charmers Pending
Review via email: mp+180209@code.launchpad.net

Description of the change

Add nagios-external-master support

This branch merges nagios-external-master support from the stable charm to this unstable charm. Conflicts had to be resolved. in 20-functional.test and in config.yaml.

https://codereview.appspot.com/12942043/

To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote :

Reviewers: mp+180209_code.launchpad.net,

Message:
Please take a look.

Description:
Add nagios-external-master support

This branch merges nagios-external-master support from the stable charm
to this unstable charm. Conflicts had to be resolved. in
20-functional.test and in config.yaml.

https://code.launchpad.net/~sinzui/charms/precise/juju-gui/unstable-nagios/+merge/180209

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/12942043/

Affected files:
   A [revision details]
   M config.yaml
   A files/nrpe-external-master/check-app-access.sh
   M metadata.yaml
   M revision
   A scripts/charmsupport/__init__.py
   A scripts/charmsupport/hookenv.py
   A scripts/charmsupport/nrpe.py
   A scripts/update-nrpe.py
   M tests/20-functional.test

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'config.yaml'
2--- config.yaml 2013-08-06 10:54:37 +0000
3+++ config.yaml 2013-08-14 18:21:07 +0000
4@@ -158,7 +158,7 @@
5 canvas. This is also known as browse mode.
6 - 'minimized': the charmbrowser will be minimized by default, and hidden.
7 type: string
8- default: sidebar
9+ default: sidebar
10 show-get-juju-button:
11 description: |
12 There are deployment modes for Juju GUI which are not intended as regular
13@@ -173,3 +173,13 @@
14 the only server in the future.
15 type: boolean
16 default: false
17+ nagios_context:
18+ description: |
19+ Used by the nrpe-external-master subordinate charm.
20+ A string that will be prepended to instance name to set the host name
21+ in nagios. So for instance the hostname would be something like:
22+ juju-myservice-0
23+ If you're running multiple environments with the same services in them
24+ this allows you to differentiate between them.
25+ type: string
26+ default: "juju"
27
28=== added directory 'files'
29=== added directory 'files/nrpe-external-master'
30=== added file 'files/nrpe-external-master/check-app-access.sh'
31--- files/nrpe-external-master/check-app-access.sh 1970-01-01 00:00:00 +0000
32+++ files/nrpe-external-master/check-app-access.sh 2013-08-14 18:21:07 +0000
33@@ -0,0 +1,18 @@
34+#!/bin/bash
35+SITE_CONF='/etc/apache2/sites-enabled/juju-gui'
36+ADDRESS='https://127.0.0.1:443/juju-ui/version.js'
37+LIFE_SIGN='jujuGuiVersionInfo'
38+
39+if [[ ! -f $SITE_CONF ]]; then
40+ echo Apache is not configured serve juju-gui.
41+ exit 2
42+fi
43+
44+match=$(curl -k $ADDRESS | grep "$LIFE_SIGN")
45+
46+if [[ -n "$match" ]]; then
47+ exit 0
48+else
49+ echo juju-gui did not return content indicating it was loading.
50+ exit 2
51+fi
52
53=== added symlink 'hooks/nrpe-external-master-relation-changed'
54=== target is u'../scripts/update-nrpe.py'
55=== modified file 'metadata.yaml'
56--- metadata.yaml 2013-06-11 14:13:45 +0000
57+++ metadata.yaml 2013-08-14 18:21:07 +0000
58@@ -22,3 +22,6 @@
59 provides:
60 web:
61 interface: http
62+ nrpe-external-master:
63+ interface: nrpe-external-master
64+ scope: container
65
66=== modified file 'revision'
67--- revision 2013-08-08 14:13:41 +0000
68+++ revision 2013-08-14 18:21:07 +0000
69@@ -1,1 +1,1 @@
70-68
71+69
72
73=== added directory 'scripts'
74=== added directory 'scripts/charmsupport'
75=== added file 'scripts/charmsupport/__init__.py'
76=== added file 'scripts/charmsupport/hookenv.py'
77--- scripts/charmsupport/hookenv.py 1970-01-01 00:00:00 +0000
78+++ scripts/charmsupport/hookenv.py 2013-08-14 18:21:07 +0000
79@@ -0,0 +1,150 @@
80+"Interactions with the Juju environment"
81+# source: 27:lp:charmsupport
82+# Copyright 2012 Canonical Ltd.
83+#
84+# Authors:
85+# Matthew Wedgwood <matthew.wedgwood@canonical.com>
86+
87+import os
88+import json
89+import yaml
90+import subprocess
91+
92+CRITICAL = "CRITICAL"
93+ERROR = "ERROR"
94+WARNING = "WARNING"
95+INFO = "INFO"
96+DEBUG = "DEBUG"
97+def log(message, level=DEBUG):
98+ "Write a message to the juju log"
99+ subprocess.call( [ 'juju-log', '-l', level, message ] )
100+
101+class Serializable(object):
102+ "Wrapper, an object that can be serialized to yaml or json"
103+ def __init__(self, obj):
104+ # wrap the object
105+ super(Serializable, self).__init__()
106+ self._wrapped_obj = obj
107+
108+ def __getattr__(self, attr):
109+ # see if this object has attr
110+ if attr in self.__dict__:
111+ return getattr(self, attr)
112+ # proxy to the wrapped object
113+ return self[attr]
114+
115+ def __getitem__(self, key):
116+ return self._wrapped_obj[key]
117+
118+ def json(self):
119+ "Serialize the object to json"
120+ return json.dumps(self._wrapped_obj)
121+
122+ def yaml(self):
123+ "Serialize the object to yaml"
124+ return yaml.dump(self._wrapped_obj)
125+
126+def execution_environment():
127+ """A convenient bundling of the current execution context"""
128+ context = {}
129+ context['conf'] = config()
130+ context['unit'] = local_unit()
131+ context['rel'] = relations_of_type()
132+ context['env'] = os.environ
133+ return context
134+
135+def in_relation_hook():
136+ "Determine whether we're running in a relation hook"
137+ return os.environ.has_key('JUJU_RELATION')
138+
139+def relation_type():
140+ "The scope for the current relation hook"
141+ return os.environ['JUJU_RELATION']
142+def relation_id():
143+ "The relation ID for the current relation hook"
144+ return os.environ['JUJU_RELATION_ID']
145+def local_unit():
146+ "Local unit ID"
147+ return os.environ['JUJU_UNIT_NAME']
148+def remote_unit():
149+ "The remote unit for the current relation hook"
150+ return os.environ['JUJU_REMOTE_UNIT']
151+
152+def config(scope=None):
153+ "Juju charm configuration"
154+ config_cmd_line = ['config-get']
155+ if scope is not None:
156+ config_cmd_line.append(scope)
157+ config_cmd_line.append('--format=json')
158+ try:
159+ config_data = json.loads(subprocess.check_output(config_cmd_line))
160+ except (ValueError, OSError, subprocess.CalledProcessError) as err:
161+ log(str(err), level=ERROR)
162+ raise err
163+ return Serializable(config_data)
164+
165+def relation_ids(reltype=None):
166+ "A list of relation_ids"
167+ reltype = reltype or relation_type()
168+ relids = []
169+ relid_cmd_line = ['relation-ids', '--format=json', reltype]
170+ relids.extend(json.loads(subprocess.check_output(relid_cmd_line)))
171+ return relids
172+
173+def related_units(relid=None):
174+ "A list of related units"
175+ relid = relid or relation_id()
176+ units_cmd_line = ['relation-list', '--format=json', '-r', relid]
177+ units = json.loads(subprocess.check_output(units_cmd_line))
178+ return units
179+
180+def relation_for_unit(unit=None):
181+ "Get the json represenation of a unit's relation"
182+ unit = unit or remote_unit()
183+ relation_cmd_line = ['relation-get', '--format=json', '-', unit]
184+ try:
185+ relation = json.loads(subprocess.check_output(relation_cmd_line))
186+ except (ValueError, OSError, subprocess.CalledProcessError), err:
187+ log(str(err), level=ERROR)
188+ raise err
189+ for key in relation:
190+ if key.endswith('-list'):
191+ relation[key] = relation[key].split()
192+ relation['__unit__'] = unit
193+ return Serializable(relation)
194+
195+def relations_for_id(relid=None):
196+ "Get relations of a specific relation ID"
197+ relation_data = []
198+ relid = relid or relation_ids()
199+ for unit in related_units(relid):
200+ unit_data = relation_for_unit(unit)
201+ unit_data['__relid__'] = relid
202+ relation_data.append(unit_data)
203+ return relation_data
204+
205+def relations_of_type(reltype=None):
206+ "Get relations of a specific type"
207+ relation_data = []
208+ if in_relation_hook():
209+ reltype = reltype or relation_type()
210+ for relid in relation_ids(reltype):
211+ for relation in relations_for_id(relid):
212+ relation['__relid__'] = relid
213+ relation_data.append(relation)
214+ return relation_data
215+
216+class UnregisteredHookError(Exception): pass
217+
218+class Hooks(object):
219+ def __init__(self):
220+ super(Hooks, self).__init__()
221+ self._hooks = {}
222+ def register(self, name, function):
223+ self._hooks[name] = function
224+ def execute(self, args):
225+ hook_name = os.path.basename(args[0])
226+ if hook_name in self._hooks:
227+ self._hooks[hook_name]()
228+ else:
229+ raise UnregisteredHookError(hook_name)
230
231=== added file 'scripts/charmsupport/nrpe.py'
232--- scripts/charmsupport/nrpe.py 1970-01-01 00:00:00 +0000
233+++ scripts/charmsupport/nrpe.py 2013-08-14 18:21:07 +0000
234@@ -0,0 +1,169 @@
235+"""Compatibility with the nrpe-external-master charm"""
236+# source: 27:lp:charmsupport
237+# Copyright 2012 Canonical Ltd.
238+#
239+# Authors:
240+# Matthew Wedgwood <matthew.wedgwood@canonical.com>
241+
242+import subprocess
243+import pwd
244+import grp
245+import os
246+import re
247+import shlex
248+
249+from hookenv import config, local_unit
250+
251+# This module adds compatibility with the nrpe_external_master
252+# subordinate charm. To use it in your charm:
253+#
254+# 1. Update metadata.yaml
255+#
256+# provides:
257+# (...)
258+# nrpe-external-master:
259+# interface: nrpe-external-master
260+# scope: container
261+#
262+# 2. Add the following to config.yaml
263+#
264+# nagios_context:
265+# default: "juju"
266+# type: string
267+# description: |
268+# Used by the nrpe-external-master subordinate charm.
269+# A string that will be prepended to instance name to set the host name
270+# in nagios. So for instance the hostname would be something like:
271+# juju-myservice-0
272+# If you're running multiple environments with the same services in them
273+# this allows you to differentiate between them.
274+#
275+# 3. Add custom checks (Nagios plugins) to files/nrpe-external-master
276+#
277+# 4. Update your hooks.py with something like this:
278+#
279+# import nrpe
280+# (...)
281+# def update_nrpe_config():
282+# nrpe_compat = NRPE("myservice")
283+# nrpe_compat.add_check(
284+# shortname = "myservice",
285+# description = "Check MyService",
286+# check_cmd = "check_http -w 2 -c 10 http://localhost"
287+# )
288+# nrpe_compat.add_check(
289+# "myservice_other",
290+# "Check for widget failures",
291+# check_cmd = "/srv/myapp/scripts/widget_check"
292+# )
293+# nrpe_compat.write()
294+#
295+# def config_changed():
296+# (...)
297+# update_nrpe_config()
298+# def nrpe_external_master_relation_changed():
299+# update_nrpe_config()
300+#
301+# 5. ln -s hooks.py nrpe-external-master-relation-changed
302+
303+class CheckException(Exception): pass
304+class Check(object):
305+ shortname_re = '[A-Za-z0-9-_]*'
306+ service_template = """
307+#---------------------------------------------------
308+# This file is Juju managed
309+#---------------------------------------------------
310+define service {{
311+ use active-service
312+ host_name {nagios_hostname}
313+ service_description {nagios_hostname}[{shortname}] {description}
314+ check_command check_nrpe!check_{shortname}
315+ servicegroups {nagios_servicegroup}
316+}}
317+"""
318+ def __init__(self, shortname, description, check_cmd):
319+ super(Check, self).__init__()
320+ # XXX: could be better to calculate this from the service name
321+ if not re.match(self.shortname_re, shortname):
322+ raise CheckException("shortname must match {}".format(Check.shortname_re))
323+ self.shortname = shortname
324+ # Note: a set of invalid characters is defined by the Nagios server config
325+ # The default is: illegal_object_name_chars=`~!$%^&*"|'<>?,()=
326+ self.description = description
327+ self.check_cmd = self._locate_cmd(check_cmd)
328+
329+ def _locate_cmd(self, check_cmd):
330+ search_path = (
331+ '/',
332+ os.path.join(os.environ['CHARM_DIR'], 'files/nrpe-external-master'),
333+ '/usr/lib/nagios/plugins',
334+ )
335+ command = shlex.split(check_cmd)
336+ for path in search_path:
337+ if os.path.exists(os.path.join(path,command[0])):
338+ return os.path.join(path, command[0]) + " " + " ".join(command[1:])
339+ subprocess.call(['juju-log', 'Check command not found: {}'.format(command[0])])
340+ return ''
341+
342+ def write(self, nagios_context, hostname):
343+ for f in os.listdir(NRPE.nagios_exportdir):
344+ if re.search('.*check_{}.cfg'.format(self.shortname), f):
345+ os.remove(os.path.join(NRPE.nagios_exportdir, f))
346+
347+ templ_vars = {
348+ 'nagios_hostname': hostname,
349+ 'nagios_servicegroup': nagios_context,
350+ 'description': self.description,
351+ 'shortname': self.shortname,
352+ }
353+ nrpe_service_text = Check.service_template.format(**templ_vars)
354+ nrpe_service_file = '{}/service__{}_check_{}.cfg'.format(
355+ NRPE.nagios_exportdir, hostname, self.shortname)
356+ with open(nrpe_service_file, 'w') as nrpe_service_config:
357+ nrpe_service_config.write(str(nrpe_service_text))
358+
359+ nrpe_check_file = '/etc/nagios/nrpe.d/check_{}.cfg'.format(self.shortname)
360+ with open(nrpe_check_file, 'w') as nrpe_check_config:
361+ nrpe_check_config.write("# check {}\n".format(self.shortname))
362+ nrpe_check_config.write("command[check_{}]={}\n".format(
363+ self.shortname, self.check_cmd))
364+
365+ def run(self):
366+ subprocess.call(self.check_cmd)
367+
368+class NRPE(object):
369+ nagios_logdir = '/var/log/nagios'
370+ nagios_exportdir = '/var/lib/nagios/export'
371+ nrpe_confdir = '/etc/nagios/nrpe.d'
372+ def __init__(self):
373+ super(NRPE, self).__init__()
374+ self.config = config()
375+ self.nagios_context = self.config['nagios_context']
376+ self.unit_name = local_unit().replace('/', '-')
377+ self.hostname = "{}-{}".format(self.nagios_context, self.unit_name)
378+ self.checks = []
379+
380+ def add_check(self, *args, **kwargs):
381+ self.checks.append( Check(*args, **kwargs) )
382+
383+ def write(self):
384+ try:
385+ nagios_uid = pwd.getpwnam('nagios').pw_uid
386+ nagios_gid = grp.getgrnam('nagios').gr_gid
387+ except:
388+ subprocess.call(['juju-log', "Nagios user not set up, nrpe checks not updated"])
389+ return
390+
391+ if not os.path.exists(NRPE.nagios_exportdir):
392+ subprocess.call(['juju-log', 'Exiting as {} is not accessible'.format(NRPE.nagios_exportdir)])
393+ return
394+
395+ if not os.path.exists(NRPE.nagios_logdir):
396+ os.mkdir(NRPE.nagios_logdir)
397+ os.chown(NRPE.nagios_logdir, nagios_uid, nagios_gid)
398+
399+ for nrpecheck in self.checks:
400+ nrpecheck.write(self.nagios_context, self.hostname)
401+
402+ if os.path.isfile('/etc/init.d/nagios-nrpe-server'):
403+ subprocess.call(['service', 'nagios-nrpe-server', 'reload'])
404
405=== added file 'scripts/update-nrpe.py'
406--- scripts/update-nrpe.py 1970-01-01 00:00:00 +0000
407+++ scripts/update-nrpe.py 2013-08-14 18:21:07 +0000
408@@ -0,0 +1,14 @@
409+#!/usr/bin/env python
410+from charmsupport import nrpe
411+
412+
413+def update_nrpe_config():
414+ nrpe_compat = nrpe.NRPE()
415+ nrpe_compat.add_check(
416+ 'App is accessible', 'Check the app can be downloaded',
417+ 'check-app-access.sh')
418+ nrpe_compat.write()
419+
420+
421+if __name__ == '__main__':
422+ update_nrpe_config()
423
424=== modified file 'tests/20-functional.test'
425--- tests/20-functional.test 2013-08-08 13:53:22 +0000
426+++ tests/20-functional.test 2013-08-14 18:21:07 +0000
427@@ -226,6 +226,17 @@
428 self.handle_browser_warning()
429 self.assertEnvironmentIsConnected()
430
431+ def test_nrpe_check_available(self):
432+ # Make sure the check-app-access.sh script's ADDRESS is available.
433+ unit_info = self.juju_deploy(
434+ self.charm, options={'juju-gui-source': JUJU_GUI_TEST_BRANCH})
435+ hostname = unit_info['public-address']
436+ conn = httplib.HTTPSConnection(hostname)
437+ # This request matches the ADDRESS var in the script.
438+ conn.request('GET', '/juju-ui/version.js')
439+ message = 'ADDRESS in check-app-access.sh is not accessible.'
440+ self.assertEqual(200, conn.getresponse().status, message)
441+
442
443 if __name__ == '__main__':
444 unittest.main(verbosity=2)

Subscribers

People subscribed via source and target branches

to all changes: