Merge lp:~ubuntuone-pqm-team/charmsupport/trunk into lp:charmsupport
- trunk
- Merge into trunk
Proposed by
Sidnei da Silva
Status: | Merged |
---|---|
Approved by: | Matthew Wedgwood |
Approved revision: | 48 |
Merged at revision: | 35 |
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Matthew Wedgwood (community) | Approve | ||
Review via email: mp+156712@code.launchpad.net |
This proposal supersedes a proposal from 2013-03-13.
Commit message
Support both the nrpe-external-
Description of the change
Support both the nrpe-external-
To post a comment you must log in.
Revision history for this message
Matthew Wedgwood (mew) wrote : Posted in a previous version of this proposal | # |
review:
Needs Fixing
Revision history for this message
Sidnei da Silva (sidnei) wrote : Posted in a previous version of this proposal | # |
Fixed. After merging, we should start looking at getting this built into a PPA.
Revision history for this message
Matthew Wedgwood (mew) : | # |
review:
Approve
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:37: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:37: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:37: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:37: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:37: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:37: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]) |
This looks good overall.
One problem I found, this fails when JUJU_UNIT_NAME is set in the environment:
NRPETestCase init_gets_ config assertEqual( 'testunit' , checker.unit_name)
test_
self.