import os import yaml import subprocess from testtools import TestCase from mock import patch, call, MagicMock from charmhelpers.contrib.charmsupport import nrpe from charmhelpers.core import host class NRPEBaseTestCase(TestCase): patches = { 'config': {'object': nrpe}, 'copy2': {'object': nrpe.shutil}, 'log': {'object': nrpe}, 'getpwnam': {'object': nrpe.pwd}, 'getgrnam': {'object': nrpe.grp}, 'glob': {'object': nrpe.glob}, 'mkdir': {'object': os}, 'chown': {'object': os}, 'chmod': {'object': os}, 'exists': {'object': os.path}, 'listdir': {'object': os}, 'remove': {'object': os}, 'open': {'object': nrpe, 'create': True}, 'isfile': {'object': os.path}, 'isdir': {'object': os.path}, 'call': {'object': subprocess}, 'relation_get': {'object': nrpe}, 'relation_ids': {'object': nrpe}, 'relation_set': {'object': nrpe}, 'relations_of_type': {'object': nrpe}, 'service': {'object': nrpe}, 'init_is_systemd': {'object': host}, } def setUp(self): super(NRPEBaseTestCase, self).setUp() self.patched = {} # Mock the universe. for attr, data in self.patches.items(): create = data.get('create', False) patcher = patch.object(data['object'], attr, create=create) self.patched[attr] = patcher.start() self.addCleanup(patcher.stop) env_patcher = patch.dict('os.environ', {'JUJU_UNIT_NAME': 'testunit', 'CHARM_DIR': '/usr/lib/test_charm_dir'}) env_patcher.start() self.addCleanup(env_patcher.stop) def check_call_counts(self, **kwargs): for attr, expected in kwargs.items(): patcher = self.patched[attr] self.assertEqual(expected, patcher.call_count, attr) class NRPETestCase(NRPEBaseTestCase): def test_init_gets_config(self): self.patched['config'].return_value = {'nagios_context': 'testctx', 'nagios_servicegroups': 'testsgrps'} checker = nrpe.NRPE() self.assertEqual('testctx', checker.nagios_context) self.assertEqual('testsgrps', checker.nagios_servicegroups) self.assertEqual('testunit', checker.unit_name) self.assertEqual('testctx-testunit', checker.hostname) self.check_call_counts(config=1) def test_init_hostname(self): """Test that the hostname parameter is correctly set""" checker = nrpe.NRPE() self.assertEqual(checker.hostname, "{}-{}".format(checker.nagios_context, checker.unit_name)) hostname = "test.host" checker = nrpe.NRPE(hostname=hostname) self.assertEqual(checker.hostname, hostname) def test_default_servicegroup(self): """Test that nagios_servicegroups gets set to the default if omitted""" self.patched['config'].return_value = {'nagios_context': 'testctx'} checker = nrpe.NRPE() self.assertEqual(checker.nagios_servicegroups, 'testctx') def test_no_nagios_installed_bails(self): self.patched['config'].return_value = {'nagios_context': 'test', 'nagios_servicegroups': ''} self.patched['getgrnam'].side_effect = KeyError checker = nrpe.NRPE() self.assertEqual(None, checker.write()) expected = 'Nagios user not set up, nrpe checks not updated' self.patched['log'].assert_called_with(expected) self.check_call_counts(log=2, config=1, getpwnam=1, getgrnam=1) def test_write_no_checker(self): self.patched['config'].return_value = {'nagios_context': 'test', 'nagios_servicegroups': ''} self.patched['exists'].return_value = True checker = nrpe.NRPE() self.assertEqual(None, checker.write()) self.check_call_counts(config=1, getpwnam=1, getgrnam=1, exists=1) def test_write_restarts_service(self): self.patched['config'].return_value = {'nagios_context': 'test', 'nagios_servicegroups': ''} self.patched['exists'].return_value = True checker = nrpe.NRPE() self.assertEqual(None, checker.write()) self.patched['service'].assert_called_with('restart', 'nagios-nrpe-server') self.check_call_counts(config=1, getpwnam=1, getgrnam=1, exists=1, service=1) def test_update_nrpe(self): self.patched['config'].return_value = {'nagios_context': 'a', 'nagios_servicegroups': ''} self.patched['exists'].return_value = True self.patched['relation_get'].return_value = { 'egress-subnets': '10.66.111.24/32', 'ingress-address': '10.66.111.24', 'private-address': '10.66.111.24' } def _rels(rname): relations = { 'local-monitors': 'local-monitors:1', 'nrpe-external-master': 'nrpe-external-master:2', } return [relations[rname]] self.patched['relation_ids'].side_effect = _rels checker = nrpe.NRPE() checker.add_check(shortname="myservice", description="Check MyService", check_cmd="check_http http://localhost") self.assertEqual(None, checker.write()) self.assertEqual(2, self.patched['open'].call_count) filename = 'check_myservice.cfg' expected = [ ('/etc/nagios/nrpe.d/%s' % filename, 'w'), ('/var/lib/nagios/export/service__a-testunit_%s' % filename, 'w'), ] actual = [x[0] for x in self.patched['open'].call_args_list] self.assertEqual(expected, actual) outfile = self.patched['open'].return_value.__enter__.return_value service_file_contents = """ #--------------------------------------------------- # This file is Juju managed #--------------------------------------------------- define service { use active-service host_name a-testunit service_description a-testunit[myservice] Check MyService check_command check_nrpe!check_myservice servicegroups a } """ expected = [ '# check myservice\n', '# The following header was added automatically by juju\n', '# Modifying it will affect nagios monitoring and alerting\n', '# servicegroups: a\n', 'command[check_myservice]=/usr/lib/nagios/plugins/check_http http://localhost\n', service_file_contents, ] actual = [x[0][0] for x in outfile.write.call_args_list] self.assertEqual(expected, actual) nrpe_monitors = {'myservice': {'command': 'check_myservice', }} monitors = yaml.dump( {"monitors": {"remote": {"nrpe": nrpe_monitors}}}) relation_set_calls = [ call(monitors=monitors, relation_id="local-monitors:1"), call(monitors=monitors, relation_id="nrpe-external-master:2"), ] self.patched['relation_set'].assert_has_calls(relation_set_calls, any_order=True) self.check_call_counts(config=1, getpwnam=1, getgrnam=1, exists=4, open=2, listdir=1, relation_get=2, relation_ids=3, relation_set=3) def test_max_check_attmpts(self): self.patched['config'].return_value = {'nagios_context': 'a', 'nagios_servicegroups': ''} self.patched['exists'].return_value = True self.patched['relation_get'].return_value = { 'egress-subnets': '10.66.111.24/32', 'ingress-address': '10.66.111.24', 'private-address': '10.66.111.24' } def _rels(rname): relations = { 'local-monitors': 'local-monitors:1', 'nrpe-external-master': 'nrpe-external-master:2', } return [relations[rname]] self.patched['relation_ids'].side_effect = _rels checker = nrpe.NRPE() checker.add_check(shortname="myservice", description="Check MyService", check_cmd="check_http http://localhost", max_check_attempts=8, ) self.assertEqual(None, checker.write()) self.assertEqual(2, self.patched['open'].call_count) filename = 'check_myservice.cfg' expected = [ ('/etc/nagios/nrpe.d/%s' % filename, 'w'), ('/var/lib/nagios/export/service__a-testunit_%s' % filename, 'w'), ] actual = [x[0] for x in self.patched['open'].call_args_list] self.assertEqual(expected, actual) outfile = self.patched['open'].return_value.__enter__.return_value service_file_contents = """ #--------------------------------------------------- # This file is Juju managed #--------------------------------------------------- define service { use active-service host_name a-testunit service_description a-testunit[myservice] Check MyService check_command check_nrpe!check_myservice servicegroups a max_check_attempts 8 } """ expected = [ '# check myservice\n', '# The following header was added automatically by juju\n', '# Modifying it will affect nagios monitoring and alerting\n', '# servicegroups: a\n', 'command[check_myservice]=/usr/lib/nagios/plugins/check_http http://localhost\n', service_file_contents, ] actual = [x[0][0] for x in outfile.write.call_args_list] self.assertEqual(expected, actual) nrpe_monitors = {'myservice': {'command': 'check_myservice', 'max_check_attempts': 8, }} monitors = yaml.dump( {"monitors": {"remote": {"nrpe": nrpe_monitors}}}) relation_set_calls = [ call(monitors=monitors, relation_id="local-monitors:1"), call(monitors=monitors, relation_id="nrpe-external-master:2"), ] self.patched['relation_set'].assert_has_calls(relation_set_calls, any_order=True) self.check_call_counts(config=1, getpwnam=1, getgrnam=1, exists=4, open=2, listdir=1, relation_get=2, relation_ids=3, relation_set=3) class NRPECheckTestCase(NRPEBaseTestCase): def test_invalid_shortname(self): cases = [ 'invalid:name', '', ] for shortname in cases: self.assertRaises(nrpe.CheckException, nrpe.Check, shortname, 'description', '/some/command') def test_valid_shortname(self): cases = [ '1_number_is_fine', 'dots.are.good', 'dashes-ok', 'UPPER_case_allowed', '5', '@valid', ] for shortname in cases: check = nrpe.Check(shortname, 'description', '/some/command') self.assertEqual(shortname, check.shortname) def test_write_removes_existing_config(self): self.patched['listdir'].return_value = [ 'foo', 'bar.cfg', '_check_shortname.cfg'] check = nrpe.Check('shortname', 'description', '/some/command') self.assertEqual(None, check.write('testctx', 'hostname', 'testsgrp')) expected = '/var/lib/nagios/export/_check_shortname.cfg' self.patched['remove'].assert_called_once_with(expected) self.check_call_counts(exists=3, remove=1, open=2, listdir=1) def test_check_write_nrpe_exportdir_not_accessible(self): self.patched['exists'].return_value = False check = nrpe.Check('shortname', 'description', '/some/command') self.assertEqual(None, check.write('testctx', 'hostname', 'testsgrps')) expected = ('Not writing service config as ' '/var/lib/nagios/export is not accessible') self.patched['log'].assert_has_calls( [call(expected)], any_order=True) self.check_call_counts(log=2, open=1) def test_locate_cmd_no_args(self): self.patched['exists'].return_value = True check = nrpe.Check('shortname', 'description', '/bin/ls') self.assertEqual('/bin/ls', check.check_cmd) def test_locate_cmd_not_found(self): self.patched['exists'].return_value = False check = nrpe.Check('shortname', 'description', 'check_http -x -y -z') self.assertEqual('', check.check_cmd) self.assertEqual(2, self.patched['exists'].call_count) expected = [ '/usr/lib/nagios/plugins/check_http', '/usr/local/lib/nagios/plugins/check_http', ] actual = [x[0][0] for x in self.patched['exists'].call_args_list] self.assertEqual(expected, actual) self.check_call_counts(exists=2, log=1) expected = 'Check command not found: check_http' self.assertEqual(expected, self.patched['log'].call_args[0][0]) def test_run(self): self.patched['exists'].return_value = True command = '/usr/bin/wget foo' check = nrpe.Check('shortname', 'description', command) self.assertEqual(None, check.run()) self.check_call_counts(exists=1, call=1) self.assertEqual(command, self.patched['call'].call_args[0][0]) class NRPEMiscTestCase(NRPEBaseTestCase): def test_get_nagios_hostcontext(self): rel_info = { 'nagios_hostname': 'bob-openstack-dashboard-0', 'private-address': '10.5.3.103', '__unit__': u'dashboard-nrpe/1', '__relid__': u'nrpe-external-master:2', 'nagios_host_context': u'bob', } self.patched['relations_of_type'].return_value = [rel_info] self.assertEqual(nrpe.get_nagios_hostcontext(), 'bob') def test_get_nagios_hostname(self): rel_info = { 'nagios_hostname': 'bob-openstack-dashboard-0', 'private-address': '10.5.3.103', '__unit__': u'dashboard-nrpe/1', '__relid__': u'nrpe-external-master:2', 'nagios_host_context': u'bob', } self.patched['relations_of_type'].return_value = [rel_info] self.assertEqual(nrpe.get_nagios_hostname(), 'bob-openstack-dashboard-0') def test_get_nagios_unit_name(self): rel_info = { 'nagios_hostname': 'bob-openstack-dashboard-0', 'private-address': '10.5.3.103', '__unit__': u'dashboard-nrpe/1', '__relid__': u'nrpe-external-master:2', 'nagios_host_context': u'bob', } self.patched['relations_of_type'].return_value = [rel_info] self.assertEqual(nrpe.get_nagios_unit_name(), 'bob:testunit') def test_get_nagios_unit_name_no_hc(self): self.patched['relations_of_type'].return_value = [] self.assertEqual(nrpe.get_nagios_unit_name(), 'testunit') @patch.object(os.path, 'isdir') def test_add_init_service_checks(self, mock_isdir): def _exists(init_file): files = ['/etc/init/apache2.conf', '/usr/lib/nagios/plugins/check_upstart_job', '/etc/init.d/haproxy', '/usr/lib/nagios/plugins/check_status_file.py', '/etc/cron.d/nagios-service-check-haproxy', '/var/lib/nagios/service-check-haproxy.txt', '/usr/lib/nagios/plugins/check_systemd.py' ] return init_file in files self.patched['exists'].side_effect = _exists # Test without systemd and /var/lib/nagios does not exist self.patched['init_is_systemd'].return_value = False mock_isdir.return_value = False bill = nrpe.NRPE() services = ['apache2', 'haproxy'] nrpe.add_init_service_checks(bill, services, 'testunit') mock_isdir.assert_called_with('/var/lib/nagios') self.patched['call'].assert_not_called() expect_cmds = { 'apache2': '/usr/lib/nagios/plugins/check_upstart_job apache2', 'haproxy': '/usr/lib/nagios/plugins/check_status_file.py -f ' '/var/lib/nagios/service-check-haproxy.txt', } self.assertEqual(bill.checks[0].shortname, 'apache2') self.assertEqual(bill.checks[0].check_cmd, expect_cmds['apache2']) self.assertEqual(bill.checks[1].shortname, 'haproxy') self.assertEqual(bill.checks[1].check_cmd, expect_cmds['haproxy']) # without systemd and /var/lib/nagios does exist mock_isdir.return_value = True f = MagicMock() self.patched['open'].return_value = f bill = nrpe.NRPE() services = ['apache2', 'haproxy'] nrpe.add_init_service_checks(bill, services, 'testunit') mock_isdir.assert_called_with('/var/lib/nagios') self.patched['call'].assert_called_with( ['/usr/local/lib/nagios/plugins/check_exit_status.pl', '-e', '-s', '/etc/init.d/haproxy', 'status'], stdout=f, stderr=subprocess.STDOUT) # Test regular services and snap services with systemd services = ['apache2', 'haproxy', 'snap.test.test', 'ceph-radosgw@hostname'] self.patched['init_is_systemd'].return_value = True nrpe.add_init_service_checks(bill, services, 'testunit') expect_cmds = { 'apache2': '/usr/lib/nagios/plugins/check_systemd.py apache2', 'haproxy': '/usr/lib/nagios/plugins/check_systemd.py haproxy', 'snap.test.test': '/usr/lib/nagios/plugins/check_systemd.py snap.test.test', } self.assertEqual(bill.checks[2].shortname, 'apache2') self.assertEqual(bill.checks[2].check_cmd, expect_cmds['apache2']) self.assertEqual(bill.checks[3].shortname, 'haproxy') self.assertEqual(bill.checks[3].check_cmd, expect_cmds['haproxy']) self.assertEqual(bill.checks[4].shortname, 'snap.test.test') self.assertEqual(bill.checks[4].check_cmd, expect_cmds['snap.test.test']) def test_copy_nrpe_checks(self): file_presence = { 'filea': True, 'fileb': False} self.patched['exists'].return_value = True self.patched['glob'].return_value = ['filea', 'fileb'] self.patched['isdir'].side_effect = [False, True] self.patched['isfile'].side_effect = lambda x: file_presence[x] nrpe.copy_nrpe_checks() self.patched['glob'].assert_called_once_with( ('/usr/lib/test_charm_dir/hooks/charmhelpers/contrib/openstack/' 'files/check_*')) self.patched['copy2'].assert_called_once_with( 'filea', '/usr/local/lib/nagios/plugins/filea') def test_copy_nrpe_checks_other_root(self): file_presence = { 'filea': True, 'fileb': False} self.patched['exists'].return_value = True self.patched['glob'].return_value = ['filea', 'fileb'] self.patched['isdir'].side_effect = [True, False] self.patched['isfile'].side_effect = lambda x: file_presence[x] nrpe.copy_nrpe_checks() self.patched['glob'].assert_called_once_with( ('/usr/lib/test_charm_dir/charmhelpers/contrib/openstack/' 'files/check_*')) self.patched['copy2'].assert_called_once_with( 'filea', '/usr/local/lib/nagios/plugins/filea') def test_copy_nrpe_checks_nrpe_files_dir(self): file_presence = { 'filea': True, 'fileb': False} self.patched['exists'].return_value = True self.patched['glob'].return_value = ['filea', 'fileb'] self.patched['isfile'].side_effect = lambda x: file_presence[x] nrpe.copy_nrpe_checks(nrpe_files_dir='/other/dir') self.patched['glob'].assert_called_once_with( '/other/dir/check_*') self.patched['copy2'].assert_called_once_with( 'filea', '/usr/local/lib/nagios/plugins/filea')