Merge lp:~ubuntuone-pqm-team/charmsupport/trunk into lp:charmsupport

Proposed by Sidnei da Silva
Status: Superseded
Proposed branch: lp:~ubuntuone-pqm-team/charmsupport/trunk
Merge into: lp:charmsupport
Diff against target: 876 lines (+465/-121)
6 files modified
Makefile (+4/-2)
charmsupport/hookenv.py (+61/-39)
charmsupport/nrpe.py (+62/-33)
setup.cfg (+0/-1)
tests/test_hookenv.py (+116/-46)
tests/test_nrpe.py (+222/-0)
To merge this branch: bzr merge lp:~ubuntuone-pqm-team/charmsupport/trunk
Reviewer Review Type Date Requested Status
Matthew Wedgwood (community) Needs Fixing
Review via email: mp+153256@code.launchpad.net

This proposal has been superseded by a proposal from 2013-04-02.

Description of the change

Support both the nrpe-external-monitor and plain nrpe charms. Notify local-monitors relation if established (noop if the relation is not there). Added tests for the nrpe module.

To post a comment you must log in.
Revision history for this message
Matthew Wedgwood (mew) wrote :

This looks good overall.

One problem I found, this fails when JUJU_UNIT_NAME is set in the environment:

NRPETestCase
  test_init_gets_config
    self.assertEqual('testunit', checker.unit_name)

review: Needs Fixing
42. By Sidnei da Silva

- Patch os.environ properly so environment doesn't affect tests.

Revision history for this message
Sidnei da Silva (sidnei) wrote :

Fixed. After merging, we should start looking at getting this built into a PPA.

43. By Sidnei da Silva

- Add relation_get helper command.

44. By Sidnei da Silva

- Make relation_ids return all ids if relation type is not present nor informed.

45. By Sidnei da Silva

- Make related_units work without a relation too.

46. By Sidnei da Silva

- Fix relations_for_id to pass rid on.

47. By Sidnei da Silva

- Proxy dict attributes directly.

48. By Sidnei da Silva

- Use UserDict as base class for Serializable, make it more dict-like.

49. By Sidnei da Silva

- Missing argument needs a '-'

50. By David Ames

[r=sidnei] Fix dictionary check for volume_map
Allow non-relation context runs

51. By Sidnei da Silva

[r=sidnei] Fix relations_for_id to use relation_id not relation_ids.

Unmerged revisions

51. By Sidnei da Silva

[r=sidnei] Fix relations_for_id to use relation_id not relation_ids.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Makefile'
2--- Makefile 2013-02-08 20:18:15 +0000
3+++ Makefile 2013-04-02 22:26:22 +0000
4@@ -31,9 +31,11 @@
5 python setup.py install --user
6
7 test:
8- nosetests tests/
9+ @echo Starting tests...
10+ @nosetests tests/
11
12 lint:
13- flake8 $(PROJECT) $(TESTS)
14+ @echo Checking for Python syntax...
15+ @flake8 --ignore=E123 $(PROJECT) $(TESTS) && echo OK
16
17 build: test lint
18
19=== modified file 'charmsupport/hookenv.py'
20--- charmsupport/hookenv.py 2013-03-07 15:28:36 +0000
21+++ charmsupport/hookenv.py 2013-04-02 22:26:22 +0000
22@@ -8,13 +8,14 @@
23 import json
24 import yaml
25 import subprocess
26-
27+import UserDict
28
29 CRITICAL = "CRITICAL"
30 ERROR = "ERROR"
31 WARNING = "WARNING"
32 INFO = "INFO"
33 DEBUG = "DEBUG"
34+MARKER = object()
35
36
37 def log(message, level=None):
38@@ -26,30 +27,35 @@
39 subprocess.call(command)
40
41
42-class Serializable(object):
43+class Serializable(UserDict.IterableUserDict):
44 "Wrapper, an object that can be serialized to yaml or json"
45+
46 def __init__(self, obj):
47 # wrap the object
48- super(Serializable, self).__init__()
49- self._wrapped_obj = obj
50+ UserDict.IterableUserDict.__init__(self)
51+ self.data = obj
52
53 def __getattr__(self, attr):
54- # see if this object has attr
55- if attr in self.__dict__:
56- return getattr(self, attr)
57- # proxy to the wrapped object
58- return self[attr]
59-
60- def __getitem__(self, key):
61- return self._wrapped_obj[key]
62+ # See if this object has attribute.
63+ if attr in ("json", "yaml", "data"):
64+ return self.__dict__[attr]
65+ # Check for attribute in wrapped object.
66+ got = getattr(self.data, attr, MARKER)
67+ if got is not MARKER:
68+ return got
69+ # Proxy to the wrapped object via dict interface.
70+ try:
71+ return self.data[attr]
72+ except KeyError:
73+ raise AttributeError(attr)
74
75 def json(self):
76 "Serialize the object to json"
77- return json.dumps(self._wrapped_obj)
78+ return json.dumps(self.data)
79
80 def yaml(self):
81 "Serialize the object to yaml"
82- return yaml.dump(self._wrapped_obj)
83+ return yaml.dump(self.data)
84
85
86 def execution_environment():
87@@ -69,12 +75,12 @@
88
89 def relation_type():
90 "The scope for the current relation hook"
91- return os.environ['JUJU_RELATION']
92+ return os.environ.get('JUJU_RELATION', None)
93
94
95 def relation_id():
96 "The relation ID for the current relation hook"
97- return os.environ['JUJU_RELATION_ID']
98+ return os.environ.get('JUJU_RELATION_ID', None)
99
100
101 def local_unit():
102@@ -101,32 +107,49 @@
103 return Serializable(config_data)
104
105
106+def relation_get(attribute=None, unit=None, rid=None):
107+ _args = ['relation-get', '--format=json']
108+ if rid:
109+ _args.append('-r')
110+ _args.append(rid)
111+ if attribute is not None:
112+ _args.append(attribute)
113+ if unit:
114+ _args.append(unit)
115+ return json.loads(subprocess.check_output(_args))
116+
117+
118+def relation_set(relation_id=None, **kwargs):
119+ relation_cmd_line = ['relation-set']
120+ if relation_id is not None:
121+ relation_cmd_line.extend(('-r', relation_id))
122+ for k, v in kwargs.items():
123+ relation_cmd_line.append('{}={}'.format(k, v))
124+ subprocess.check_call(relation_cmd_line)
125+
126+
127 def relation_ids(reltype=None):
128 "A list of relation_ids"
129 reltype = reltype or relation_type()
130- relids = []
131- relid_cmd_line = ['relation-ids', '--format=json', reltype]
132- relids.extend(json.loads(subprocess.check_output(relid_cmd_line)))
133- return relids
134+ relid_cmd_line = ['relation-ids', '--format=json']
135+ if reltype is not None:
136+ relid_cmd_line.append(reltype)
137+ return json.loads(subprocess.check_output(relid_cmd_line))
138
139
140 def related_units(relid=None):
141 "A list of related units"
142 relid = relid or relation_id()
143- units_cmd_line = ['relation-list', '--format=json', '-r', relid]
144- units = json.loads(subprocess.check_output(units_cmd_line))
145- return units
146-
147-
148-def relation_for_unit(unit=None):
149+ units_cmd_line = ['relation-list', '--format=json']
150+ if relid is not None:
151+ units_cmd_line.extend(('-r', relid))
152+ return json.loads(subprocess.check_output(units_cmd_line))
153+
154+
155+def relation_for_unit(unit=None, rid=None):
156 "Get the json represenation of a unit's relation"
157 unit = unit or remote_unit()
158- relation_cmd_line = ['relation-get', '--format=json', '-', unit]
159- try:
160- relation = json.loads(subprocess.check_output(relation_cmd_line))
161- except (ValueError, OSError, subprocess.CalledProcessError), err:
162- log(str(err), level=ERROR)
163- raise
164+ relation = relation_get(unit=unit, rid=rid)
165 for key in relation:
166 if key.endswith('-list'):
167 relation[key] = relation[key].split()
168@@ -139,7 +162,7 @@
169 relation_data = []
170 relid = relid or relation_ids()
171 for unit in related_units(relid):
172- unit_data = relation_for_unit(unit)
173+ unit_data = relation_for_unit(unit, relid)
174 unit_data['__relid__'] = relid
175 relation_data.append(unit_data)
176 return relation_data
177@@ -148,12 +171,11 @@
178 def relations_of_type(reltype=None):
179 "Get relations of a specific type"
180 relation_data = []
181- if in_relation_hook():
182- reltype = reltype or relation_type()
183- for relid in relation_ids(reltype):
184- for relation in relations_for_id(relid):
185- relation['__relid__'] = relid
186- relation_data.append(relation)
187+ reltype = reltype or relation_type()
188+ for relid in relation_ids(reltype):
189+ for relation in relations_for_id(relid):
190+ relation['__relid__'] = relid
191+ relation_data.append(relation)
192 return relation_data
193
194
195
196=== modified file 'charmsupport/nrpe.py'
197--- charmsupport/nrpe.py 2013-03-07 14:59:36 +0000
198+++ charmsupport/nrpe.py 2013-04-02 22:26:22 +0000
199@@ -10,11 +10,13 @@
200 import os
201 import re
202 import shlex
203-
204-from hookenv import config, local_unit, log
205-
206-# This module adds compatibility with the nrpe_external_master
207-# subordinate charm. To use it in your charm:
208+import yaml
209+
210+from hookenv import config, local_unit, log, relation_ids, relation_set
211+from host import service
212+
213+# This module adds compatibility with the nrpe-external-master and plain nrpe
214+# subordinate charms. To use it in your charm:
215 #
216 # 1. Update metadata.yaml
217 #
218@@ -24,13 +26,22 @@
219 # interface: nrpe-external-master
220 # scope: container
221 #
222+# and/or
223+#
224+# provides:
225+# (...)
226+# local-monitors:
227+# interface: local-monitors
228+# scope: container
229+
230+#
231 # 2. Add the following to config.yaml
232 #
233 # nagios_context:
234 # default: "juju"
235 # type: string
236 # description: |
237-# Used by the nrpe-external-master subordinate charm.
238+# Used by the nrpe subordinate charms.
239 # A string that will be prepended to instance name to set the host name
240 # in nagios. So for instance the hostname would be something like:
241 # juju-myservice-0
242@@ -60,10 +71,15 @@
243 # def config_changed():
244 # (...)
245 # update_nrpe_config()
246+#
247 # def nrpe_external_master_relation_changed():
248 # update_nrpe_config()
249 #
250+# def local_monitors_relation_changed():
251+# update_nrpe_config()
252+#
253 # 5. ln -s hooks.py nrpe-external-master-relation-changed
254+# ln -s hooks.py local-monitors-relation-changed
255
256
257 class CheckException(Exception):
258@@ -71,7 +87,7 @@
259
260
261 class Check(object):
262- shortname_re = '[A-Za-z0-9-_]*'
263+ shortname_re = '[A-Za-z0-9-_]+$'
264 service_template = ("""
265 #---------------------------------------------------
266 # This file is Juju managed
267@@ -80,8 +96,8 @@
268 use active-service
269 host_name {nagios_hostname}
270 service_description {nagios_hostname}[{shortname}] """
271- """"{description}
272- check_command check_nrpe!check_{shortname}
273+ """{description}
274+ check_command check_nrpe!{command}
275 servicegroups {nagios_servicegroup}
276 }}
277 """)
278@@ -93,6 +109,7 @@
279 raise CheckException("shortname must match {}".format(
280 Check.shortname_re))
281 self.shortname = shortname
282+ self.command = "check_{}".format(shortname)
283 # Note: a set of invalid characters is defined by the
284 # Nagios server config
285 # The default is: illegal_object_name_chars=`~!$%^&*"|'<>?,()=
286@@ -106,17 +123,33 @@
287 'files/nrpe-external-master'),
288 '/usr/lib/nagios/plugins',
289 )
290- command = shlex.split(check_cmd)
291+ parts = shlex.split(check_cmd)
292 for path in search_path:
293- if os.path.exists(os.path.join(path, command[0])):
294- return os.path.join(path, command[0]) + " " + " ".join(
295- command[1:])
296- log('Check command not found: {}'.format(command[0]))
297+ if os.path.exists(os.path.join(path, parts[0])):
298+ command = os.path.join(path, parts[0])
299+ if len(parts) > 1:
300+ command += " " + " ".join(parts[1:])
301+ return command
302+ log('Check command not found: {}'.format(parts[0]))
303 return ''
304
305 def write(self, nagios_context, hostname):
306+ nrpe_check_file = '/etc/nagios/nrpe.d/{}.cfg'.format(
307+ self.command)
308+ with open(nrpe_check_file, 'w') as nrpe_check_config:
309+ nrpe_check_config.write("# check {}\n".format(self.shortname))
310+ nrpe_check_config.write("command[{}]={}\n".format(
311+ self.command, self.check_cmd))
312+
313+ if not os.path.exists(NRPE.nagios_exportdir):
314+ log('Not writing service config as {} is not accessible'.format(
315+ NRPE.nagios_exportdir))
316+ else:
317+ self.write_service_config(nagios_context, hostname)
318+
319+ def write_service_config(self, nagios_context, hostname):
320 for f in os.listdir(NRPE.nagios_exportdir):
321- if re.search('.*check_{}.cfg'.format(self.shortname), f):
322+ if re.search('.*{}.cfg'.format(self.command), f):
323 os.remove(os.path.join(NRPE.nagios_exportdir, f))
324
325 templ_vars = {
326@@ -124,20 +157,14 @@
327 'nagios_servicegroup': nagios_context,
328 'description': self.description,
329 'shortname': self.shortname,
330- }
331+ 'command': self.command,
332+ }
333 nrpe_service_text = Check.service_template.format(**templ_vars)
334- nrpe_service_file = '{}/service__{}_check_{}.cfg'.format(
335- NRPE.nagios_exportdir, hostname, self.shortname)
336+ nrpe_service_file = '{}/service__{}_{}.cfg'.format(
337+ NRPE.nagios_exportdir, hostname, self.command)
338 with open(nrpe_service_file, 'w') as nrpe_service_config:
339 nrpe_service_config.write(str(nrpe_service_text))
340
341- nrpe_check_file = '/etc/nagios/nrpe.d/check_{}.cfg'.format(
342- self.shortname)
343- with open(nrpe_check_file, 'w') as nrpe_check_config:
344- nrpe_check_config.write("# check {}\n".format(self.shortname))
345- nrpe_check_config.write("command[check_{}]={}\n".format(
346- self.shortname, self.check_cmd))
347-
348 def run(self):
349 subprocess.call(self.check_cmd)
350
351@@ -166,17 +193,19 @@
352 log("Nagios user not set up, nrpe checks not updated")
353 return
354
355- if not os.path.exists(NRPE.nagios_exportdir):
356- log('Exiting as {} is not accessible'.format(
357- NRPE.nagios_exportdir))
358- return
359-
360 if not os.path.exists(NRPE.nagios_logdir):
361 os.mkdir(NRPE.nagios_logdir)
362 os.chown(NRPE.nagios_logdir, nagios_uid, nagios_gid)
363
364+ nrpe_monitors = {}
365+ monitors = {"monitors": {"remote": {"nrpe": nrpe_monitors}}}
366 for nrpecheck in self.checks:
367 nrpecheck.write(self.nagios_context, self.hostname)
368-
369- if os.path.isfile('/etc/init.d/nagios-nrpe-server'):
370- subprocess.call(['service', 'nagios-nrpe-server', 'reload'])
371+ nrpe_monitors[nrpecheck.shortname] = {
372+ "command": nrpecheck.command,
373+ }
374+
375+ service('restart', 'nagios-nrpe-server')
376+
377+ for rid in relation_ids("local-monitors"):
378+ relation_set(relation_id=rid, monitors=yaml.dump(monitors))
379
380=== modified file 'setup.cfg'
381--- setup.cfg 2013-02-08 16:54:43 +0000
382+++ setup.cfg 2013-04-02 22:26:22 +0000
383@@ -1,5 +1,4 @@
384 [nosetests]
385 with-coverage=1
386 cover-erase=1
387-cover-xml=1
388 cover-package=charmsupport
389
390=== modified file 'tests/test_hookenv.py'
391--- tests/test_hookenv.py 2013-03-07 15:28:36 +0000
392+++ tests/test_hookenv.py 2013-04-02 22:26:22 +0000
393@@ -30,13 +30,38 @@
394
395 self.assertEqual(wrapped.bar, 'baz')
396
397+ def test_dict_methods_from_inner_object(self):
398+ foo = {
399+ 'bar': 'baz',
400+ }
401+ wrapped = hookenv.Serializable(foo)
402+ for meth in ('keys', 'values', 'items'):
403+ self.assertEqual(getattr(wrapped, meth)(), getattr(foo, meth)())
404+
405+ for meth in ('iterkeys', 'itervalues', 'iteritems'):
406+ self.assertEqual(list(getattr(wrapped, meth)()),
407+ list(getattr(foo, meth)()))
408+ self.assertEqual(wrapped.get('bar'), foo.get('bar'))
409+ self.assertEqual(wrapped.get('baz', 42), foo.get('baz', 42))
410+ self.assertIn('bar', wrapped)
411+
412+ def test_get_gets_from_inner_object(self):
413+ foo = {
414+ 'bar': 'baz',
415+ }
416+ wrapped = hookenv.Serializable(foo)
417+
418+ self.assertEqual(wrapped.get('foo'), None)
419+ self.assertEqual(wrapped.get('bar'), 'baz')
420+ self.assertEqual(wrapped.get('zoo', 'bla'), 'bla')
421+
422 def test_gets_inner_object(self):
423 foo = {
424 'bar': 'baz',
425 }
426 wrapped = hookenv.Serializable(foo)
427
428- self.assertIs(wrapped._wrapped_obj, foo)
429+ self.assertIs(wrapped.data, foo)
430
431
432 class HelpersTest(TestCase):
433@@ -119,6 +144,11 @@
434
435 self.assertEqual(hookenv.relation_type(), 'foo')
436
437+ @patch('charmsupport.hookenv.os')
438+ def test_relation_type_none_if_not_in_environment(self, os_):
439+ os_.environ = {}
440+ self.assertEqual(hookenv.relation_type(), None)
441+
442 @patch('subprocess.check_output')
443 @patch('charmsupport.hookenv.relation_type')
444 def test_gets_relation_ids(self, relation_type, check_output):
445@@ -135,6 +165,18 @@
446
447 @patch('subprocess.check_output')
448 @patch('charmsupport.hookenv.relation_type')
449+ def test_relation_ids_no_relation_type(self, relation_type, check_output):
450+ ids = [1, 2, 3]
451+ check_output.return_value = json.dumps(ids)
452+ relation_type.return_value = None
453+
454+ result = hookenv.relation_ids()
455+
456+ self.assertEqual(result, ids)
457+ check_output.assert_called_with(['relation-ids', '--format=json'])
458+
459+ @patch('subprocess.check_output')
460+ @patch('charmsupport.hookenv.relation_type')
461 def test_gets_relation_ids_for_type(self, relation_type, check_output):
462 ids = [1, 2, 3]
463 check_output.return_value = json.dumps(ids)
464@@ -163,6 +205,18 @@
465
466 @patch('subprocess.check_output')
467 @patch('charmsupport.hookenv.relation_id')
468+ def test_related_units_no_relation(self, relation_id, check_output):
469+ units = ['foo', 'bar']
470+ relation_id.return_value = None
471+ check_output.return_value = json.dumps(units)
472+
473+ result = hookenv.related_units()
474+
475+ self.assertEqual(result, units)
476+ check_output.assert_called_with(['relation-list', '--format=json'])
477+
478+ @patch('subprocess.check_output')
479+ @patch('charmsupport.hookenv.relation_id')
480 def test_gets_related_units_for_id(self, relation_id, check_output):
481 relid = 123
482 units = ['foo', 'bar']
483@@ -183,55 +237,40 @@
484
485 self.assertEqual(hookenv.remote_unit(), 'foo')
486
487- @patch('subprocess.check_output')
488 @patch('charmsupport.hookenv.remote_unit')
489- def test_gets_relation_for_unit(self, remote_unit, check_output):
490+ @patch('charmsupport.hookenv.relation_get')
491+ def test_gets_relation_for_unit(self, relation_get, remote_unit):
492 unit = 'foo-unit'
493 raw_relation = {
494 'foo': 'bar',
495 'baz-list': '1 2 3',
496 }
497 remote_unit.return_value = unit
498- check_output.return_value = json.dumps(raw_relation)
499+ relation_get.return_value = raw_relation
500
501 result = hookenv.relation_for_unit()
502
503 self.assertEqual(result.__unit__, unit)
504 self.assertEqual(getattr(result, 'baz-list'), ['1', '2', '3'])
505- check_output.assert_called_with(['relation-get', '--format=json', '-',
506- unit])
507+ relation_get.assert_called_with(unit=unit, rid=None)
508
509- @patch('subprocess.check_output')
510 @patch('charmsupport.hookenv.remote_unit')
511- def test_gets_relation_for_specific_unit(self, remote_unit, check_output):
512+ @patch('charmsupport.hookenv.relation_get')
513+ def test_gets_relation_for_specific_unit(self, relation_get, remote_unit):
514 unit = 'foo-unit'
515 raw_relation = {
516 'foo': 'bar',
517 'baz-list': '1 2 3',
518 }
519- check_output.return_value = json.dumps(raw_relation)
520+ relation_get.return_value = raw_relation
521
522 result = hookenv.relation_for_unit(unit)
523
524 self.assertEqual(result.__unit__, unit)
525 self.assertEqual(getattr(result, 'baz-list'), ['1', '2', '3'])
526- check_output.assert_called_with(['relation-get', '--format=json', '-',
527- unit])
528+ relation_get.assert_called_with(unit=unit, rid=None)
529 self.assertFalse(remote_unit.called)
530
531- @patch('subprocess.check_output')
532- @patch('charmsupport.hookenv.log')
533- def test_logs_and_reraises_error_for_relation_for_unit(self, log,
534- check_output):
535- unit = 'foo-unit'
536- error = 'some error'
537- check_output.side_effect = ValueError(error)
538-
539- self.assertRaisesRegexp(ValueError, error, hookenv.relation_for_unit,
540- unit)
541-
542- log.assert_called_with(error, level=hookenv.ERROR)
543-
544 @patch('charmsupport.hookenv.relation_ids')
545 @patch('charmsupport.hookenv.related_units')
546 @patch('charmsupport.hookenv.relation_for_unit')
547@@ -255,8 +294,8 @@
548 self.assertEqual(result[1]['foo-item2'], 'bar-item2')
549 related_units.assert_called_with(relid)
550 self.assertEqual(relation_for_unit.mock_calls, [
551- call('foo'),
552- call('bar'),
553+ call('foo', relid),
554+ call('bar', relid),
555 ])
556
557 @patch('charmsupport.hookenv.relation_ids')
558@@ -281,8 +320,8 @@
559 self.assertEqual(result[1]['foo-item2'], 'bar-item2')
560 related_units.assert_called_with(relid)
561 self.assertEqual(relation_for_unit.mock_calls, [
562- call('foo'),
563- call('bar'),
564+ call('foo', relid),
565+ call('bar', relid),
566 ])
567 self.assertFalse(relation_ids.called)
568
569@@ -327,24 +366,6 @@
570 call(234),
571 ])
572
573- @patch('charmsupport.hookenv.in_relation_hook')
574- @patch('charmsupport.hookenv.relation_type')
575- @patch('charmsupport.hookenv.relation_ids')
576- @patch('charmsupport.hookenv.relations_for_id')
577- def test_gets_nothing_for_relations_if_not_in_hook(self, relations_for_id,
578- relation_ids,
579- relation_type,
580- in_relation_hook):
581- is_in_relation = False
582- in_relation_hook.return_value = is_in_relation
583-
584- result = hookenv.relations_of_type()
585-
586- self.assertEqual(result, [])
587- self.assertFalse(relations_for_id.called)
588- self.assertFalse(relation_ids.called)
589- self.assertFalse(relation_type.called)
590-
591 @patch('charmsupport.hookenv.config')
592 @patch('charmsupport.hookenv.local_unit')
593 @patch('charmsupport.hookenv.relations_of_type')
594@@ -373,6 +394,55 @@
595
596 self.assertEqual(hookenv.relation_id(), 'foo')
597
598+ @patch('charmsupport.hookenv.os')
599+ def test_relation_id_none_if_no_env(self, os_):
600+ os_.environ = {}
601+ self.assertEqual(hookenv.relation_id(), None)
602+
603+ @patch('subprocess.check_output')
604+ def test_gets_relation(self, check_output):
605+ json_string = '{"foo": "BAR"}'
606+ check_output.return_value = json_string
607+
608+ result = hookenv.relation_get()
609+
610+ self.assertEqual(result['foo'], 'BAR')
611+ check_output.assert_called_with(['relation-get', '--format=json'])
612+
613+ @patch('subprocess.check_output')
614+ def test_gets_relation_with_scope(self, check_output):
615+ json_string = '{"foo": "BAR"}'
616+ check_output.return_value = json_string
617+
618+ result = hookenv.relation_get(attribute='baz-scope')
619+
620+ self.assertEqual(result['foo'], 'BAR')
621+ check_output.assert_called_with(['relation-get', '--format=json',
622+ 'baz-scope'])
623+
624+ @patch('subprocess.check_output')
625+ def test_gets_relation_with_unit_name(self, check_output):
626+ json_string = '{"foo": "BAR"}'
627+ check_output.return_value = json_string
628+
629+ result = hookenv.relation_get(attribute='baz-scope', unit='baz-unit')
630+
631+ self.assertEqual(result['foo'], 'BAR')
632+ check_output.assert_called_with(['relation-get', '--format=json',
633+ 'baz-scope', 'baz-unit'])
634+
635+ @patch('subprocess.check_output')
636+ def test_gets_relation_with_relation_id(self, check_output):
637+ json_string = '{"foo": "BAR"}'
638+ check_output.return_value = json_string
639+
640+ result = hookenv.relation_get(attribute='baz-scope', unit='baz-unit',
641+ rid=123)
642+
643+ self.assertEqual(result['foo'], 'BAR')
644+ check_output.assert_called_with(['relation-get', '--format=json', '-r',
645+ 123, 'baz-scope', 'baz-unit'])
646+
647
648 class HooksTest(TestCase):
649 def test_runs_a_registered_function(self):
650
651=== added file 'tests/test_nrpe.py'
652--- tests/test_nrpe.py 1970-01-01 00:00:00 +0000
653+++ tests/test_nrpe.py 2013-04-02 22:26:22 +0000
654@@ -0,0 +1,222 @@
655+import os
656+import yaml
657+import subprocess
658+
659+from testtools import TestCase
660+from mock import patch, call
661+
662+from charmsupport import nrpe
663+
664+
665+class NRPEBaseTestCase(TestCase):
666+ patches = {
667+ 'config': {'object': nrpe},
668+ 'log': {'object': nrpe},
669+ 'getpwnam': {'object': nrpe.pwd},
670+ 'getgrnam': {'object': nrpe.grp},
671+ 'mkdir': {'object': os},
672+ 'chown': {'object': os},
673+ 'exists': {'object': os.path},
674+ 'listdir': {'object': os},
675+ 'remove': {'object': os},
676+ 'open': {'object': nrpe, 'create': True},
677+ 'isfile': {'object': os.path},
678+ 'call': {'object': subprocess},
679+ 'relation_ids': {'object': nrpe},
680+ 'relation_set': {'object': nrpe},
681+ }
682+
683+ def setUp(self):
684+ super(NRPEBaseTestCase, self).setUp()
685+ self.patched = {}
686+ # Mock the universe.
687+ for attr, data in self.patches.items():
688+ create = data.get('create', False)
689+ patcher = patch.object(data['object'], attr, create=create)
690+ self.patched[attr] = patcher.start()
691+ self.addCleanup(patcher.stop)
692+ env_patcher = patch.dict('os.environ',
693+ {'JUJU_UNIT_NAME': 'testunit',
694+ 'CHARM_DIR': '/usr/lib/test_charm_dir'})
695+ env_patcher.start()
696+ self.addCleanup(env_patcher.stop)
697+
698+ def check_call_counts(self, **kwargs):
699+ for attr, expected in kwargs.items():
700+ patcher = self.patched[attr]
701+ self.assertEqual(expected, patcher.call_count, attr)
702+
703+
704+class NRPETestCase(NRPEBaseTestCase):
705+
706+ def test_init_gets_config(self):
707+ self.patched['config'].return_value = {'nagios_context': 'testctx'}
708+
709+ checker = nrpe.NRPE()
710+
711+ self.assertEqual('testctx', checker.nagios_context)
712+ self.assertEqual('testunit', checker.unit_name)
713+ self.assertEqual('testctx-testunit', checker.hostname)
714+ self.check_call_counts(config=1)
715+
716+ def test_no_nagios_installed_bails(self):
717+ self.patched['config'].return_value = {'nagios_context': 'test'}
718+ self.patched['getgrnam'].side_effect = KeyError
719+ checker = nrpe.NRPE()
720+
721+ self.assertEqual(None, checker.write())
722+
723+ expected = 'Nagios user not set up, nrpe checks not updated'
724+ self.patched['log'].assert_called_once_with(expected)
725+ self.check_call_counts(log=1, config=1, getpwnam=1, getgrnam=1)
726+
727+ def test_write_no_checker(self):
728+ self.patched['config'].return_value = {'nagios_context': 'test'}
729+ self.patched['exists'].return_value = True
730+ checker = nrpe.NRPE()
731+
732+ self.assertEqual(None, checker.write())
733+
734+ self.check_call_counts(config=1, getpwnam=1, getgrnam=1, exists=2)
735+
736+ def test_write_restarts_service(self):
737+ self.patched['config'].return_value = {'nagios_context': 'test'}
738+ self.patched['exists'].return_value = True
739+ checker = nrpe.NRPE()
740+
741+ self.assertEqual(None, checker.write())
742+
743+ expected = ['initctl', 'restart', 'nagios-nrpe-server']
744+ self.assertEqual(expected, self.patched['call'].call_args[0][0])
745+ self.check_call_counts(config=1, getpwnam=1, getgrnam=1,
746+ exists=2, call=1)
747+
748+ def test_update_nrpe(self):
749+ self.patched['config'].return_value = {'nagios_context': 'a'}
750+ self.patched['exists'].return_value = True
751+ self.patched['relation_ids'].return_value = ['local-monitors:1']
752+
753+ checker = nrpe.NRPE()
754+ checker.add_check(shortname="myservice",
755+ description="Check MyService",
756+ check_cmd="check_http http://localhost")
757+
758+ self.assertEqual(None, checker.write())
759+
760+ self.assertEqual(2, self.patched['open'].call_count)
761+ filename = 'check_myservice.cfg'
762+ expected = [
763+ ('/etc/nagios/nrpe.d/%s' % filename, 'w'),
764+ ('/var/lib/nagios/export/service__a-testunit_%s' % filename, 'w'),
765+ ]
766+ actual = [x[0] for x in self.patched['open'].call_args_list]
767+ self.assertEqual(expected, actual)
768+ outfile = self.patched['open'].return_value.__enter__.return_value
769+ service_file_contents = """
770+#---------------------------------------------------
771+# This file is Juju managed
772+#---------------------------------------------------
773+define service {
774+ use active-service
775+ host_name a-testunit
776+ service_description a-testunit[myservice] Check MyService
777+ check_command check_nrpe!check_myservice
778+ servicegroups a
779+}
780+"""
781+ expected = [
782+ '# check myservice\n',
783+ 'command[check_myservice]=/check_http http://localhost\n',
784+ service_file_contents,
785+ ]
786+ actual = [x[0][0] for x in outfile.write.call_args_list]
787+ self.assertEqual(expected, actual)
788+
789+ nrpe_monitors = {'myservice':
790+ {'command': 'check_myservice'}}
791+ monitors = yaml.dump(
792+ {"monitors": {"remote": {"nrpe": nrpe_monitors}}})
793+ self.patched['relation_set'].assert_called_once_with(
794+ relation_id="local-monitors:1", monitors=monitors)
795+ self.check_call_counts(config=1, getpwnam=1, getgrnam=1,
796+ exists=4, open=2, listdir=1,
797+ relation_ids=1, relation_set=1)
798+
799+
800+class NRPECheckTestCase(NRPEBaseTestCase):
801+
802+ def test_invalid_shortname(self):
803+ cases = [
804+ 'invalid:name',
805+ '@invalid',
806+ '',
807+ ]
808+ for shortname in cases:
809+ self.assertRaises(nrpe.CheckException, nrpe.Check, shortname,
810+ 'description', '/some/command')
811+
812+ def test_valid_shortname(self):
813+ cases = [
814+ '1_number_is_fine',
815+ 'dashes-ok',
816+ '5',
817+ ]
818+ for shortname in cases:
819+ check = nrpe.Check(shortname, 'description', '/some/command')
820+ self.assertEqual(shortname, check.shortname)
821+
822+ def test_write_removes_existing_config(self):
823+ self.patched['listdir'].return_value = [
824+ 'foo', 'bar.cfg', 'check_shortname.cfg']
825+ check = nrpe.Check('shortname', 'description', '/some/command')
826+
827+ self.assertEqual(None, check.write('testctx', 'hostname'))
828+
829+ expected = '/var/lib/nagios/export/check_shortname.cfg'
830+ self.patched['remove'].assert_called_once_with(expected)
831+ self.check_call_counts(exists=2, remove=1, open=2, listdir=1)
832+
833+ def test_check_write_nrpe_exportdir_not_accessible(self):
834+ self.patched['exists'].return_value = False
835+ check = nrpe.Check('shortname', 'description', '/some/command')
836+
837+ self.assertEqual(None, check.write('testctx', 'hostname'))
838+ expected = ('Not writing service config as '
839+ '/var/lib/nagios/export is not accessible')
840+ self.patched['log'].assert_has_calls(
841+ [call(expected)], any_order=True)
842+ self.check_call_counts(log=2, open=1)
843+
844+ def test_locate_cmd_no_args(self):
845+ self.patched['exists'].return_value = True
846+
847+ check = nrpe.Check('shortname', 'description', '/bin/ls')
848+
849+ self.assertEqual('/bin/ls', check.check_cmd)
850+
851+ def test_locate_cmd_not_found(self):
852+ self.patched['exists'].return_value = False
853+ check = nrpe.Check('shortname', 'description', 'check_http -x -y -z')
854+
855+ self.assertEqual('', check.check_cmd)
856+ self.assertEqual(3, self.patched['exists'].call_count)
857+ expected = [
858+ '/check_http',
859+ '/usr/lib/test_charm_dir/files/nrpe-external-master/check_http',
860+ '/usr/lib/nagios/plugins/check_http',
861+ ]
862+ actual = [x[0][0] for x in self.patched['exists'].call_args_list]
863+ self.assertEqual(expected, actual)
864+ self.check_call_counts(exists=3, log=1)
865+ expected = 'Check command not found: check_http'
866+ self.assertEqual(expected, self.patched['log'].call_args[0][0])
867+
868+ def test_run(self):
869+ self.patched['exists'].return_value = True
870+ command = '/usr/bin/wget foo'
871+ check = nrpe.Check('shortname', 'description', command)
872+
873+ self.assertEqual(None, check.run())
874+
875+ self.check_call_counts(exists=1, call=1)
876+ self.assertEqual(command, self.patched['call'].call_args[0][0])

Subscribers

People subscribed via source and target branches