diff --git a/vitrage/synchronizer/plugins/nagios/parser.py b/vitrage/synchronizer/plugins/nagios/parser.py index f3f499ee2..4c00c3d44 100644 --- a/vitrage/synchronizer/plugins/nagios/parser.py +++ b/vitrage/synchronizer/plugins/nagios/parser.py @@ -27,29 +27,28 @@ class NagiosParser(object): NAME_XPATH = 'table/tr/td[position()=1]/table/tr/td/a' def __init__(self): - self.last_host_name = '' - return + self.last_host_name = None def parse(self, html): - services = [] - parser = etree.HTMLParser() - try: - tree = etree.parse(StringIO(html), parser) + tree = etree.parse(StringIO(html), etree.HTMLParser()) status_tables = tree.xpath(self.STATUS_TABLE_XPATH) - for status_table in status_tables: - service_rows = status_table.xpath(self.SERVICE_ROWS_XPATH) - - for service_row in service_rows: - service = self._parse_service_row(service_row) - if service: - services.append(service) + return self._parse_services(status_tables) except Exception as e: LOG.exception('Failed to get nagios services %s', e) - return services + return None + def _parse_services(self, status_tables): + services = [] + for status_table in status_tables: + service_rows = status_table.xpath(self.SERVICE_ROWS_XPATH) + + for service_row in service_rows: + service = self._parse_service_row(service_row) + if service: + services.append(service) return services def _parse_service_row(self, service_row): @@ -59,41 +58,34 @@ class NagiosParser(object): # there are also two blank lines between different hosts, # so len(columns)==1 is also valid # TODO(ifat_afek): get column names by the header line - if (len(columns) == NagiosProperties.NUM_COLUMNS): + if len(columns) == NagiosProperties.NUM_COLUMNS: return self._parse_service_columns(columns) - elif (len(columns) > NagiosProperties.NUM_COLUMNS): + elif len(columns) > NagiosProperties.NUM_COLUMNS: LOG.warn('Too many columns in nagios service row. ' 'Found %d', len(columns)) - elif (len(columns) > 1): + elif len(columns) > 1: LOG.warn('Missing columns in nagios service row. ' 'Found only %d', len(columns)) return None def _parse_service_columns(self, columns): - host_name = self._parse_host_name(columns[0], self.NAME_XPATH) - service_name = self._get_column_data(columns[1], self.NAME_XPATH) - status = columns[2].text - last_check = columns[3].text - duration = columns[4].text - attempt = columns[5].text - status_information = columns[6].text - - service = { - NagiosProperties.RESOURCE_NAME: host_name, - NagiosProperties.SERVICE: service_name, - NagiosProperties.STATUS: status, - NagiosProperties.LAST_CHECK: last_check, - NagiosProperties.DURATION: duration, - NagiosProperties.ATTEMPT: attempt, - NagiosProperties.STATUS_INFO: status_information + return { + NagiosProperties.RESOURCE_NAME: + self._parse_host_name(columns[0], self.NAME_XPATH), + NagiosProperties.SERVICE: + self._get_column_data(columns[1], self.NAME_XPATH), + NagiosProperties.STATUS: columns[2].text, + NagiosProperties.LAST_CHECK: columns[3].text, + NagiosProperties.DURATION: columns[4].text, + NagiosProperties.ATTEMPT: columns[5].text, + NagiosProperties.STATUS_INFO: columns[6].text } - return service - - def _get_column_data(self, column, xpath): + @staticmethod + def _get_column_data(column, xpath): contents = column.xpath(xpath) if len(contents) == 1: @@ -105,7 +97,7 @@ class NagiosParser(object): else: # len(contents) might be 0 for a host, since each host name appears # only once in the table - return '' + return None def _parse_host_name(self, column, xpath): """Parse the host name and return it diff --git a/vitrage/synchronizer/plugins/nagios/synchronizer.py b/vitrage/synchronizer/plugins/nagios/synchronizer.py index dac684913..49801b40d 100644 --- a/vitrage/synchronizer/plugins/nagios/synchronizer.py +++ b/vitrage/synchronizer/plugins/nagios/synchronizer.py @@ -47,6 +47,11 @@ class NagiosSynchronizer(SynchronizerBase): return [] def _get_services(self): + nagios_services = self._get_services_from_nagios() + self._enrich_services(nagios_services) + return self._cache_and_filter_services(nagios_services) + + def _get_services_from_nagios(self): nagios_user = self.conf.synchronizer_plugins.nagios_user nagios_password = self.conf.synchronizer_plugins.nagios_password nagios_url = self.conf.synchronizer_plugins.nagios_url @@ -71,8 +76,7 @@ class NagiosSynchronizer(SynchronizerBase): if response.status_code == requests.codes.ok: nagios_services = NagiosParser().parse(response.text) - self._enrich_services(nagios_services) - return self._cache_and_filter_services(nagios_services) + return nagios_services else: LOG.error(_LE('Failed to get nagios data. Response code: %s') % response.status_code) diff --git a/vitrage/tests/resources/sync_nagios_snapshot_dynamic.json b/vitrage/tests/resources/sync_nagios_snapshot_dynamic.json index 1671aab58..41b85017c 100644 --- a/vitrage/tests/resources/sync_nagios_snapshot_dynamic.json +++ b/vitrage/tests/resources/sync_nagios_snapshot_dynamic.json @@ -8,6 +8,5 @@ "duration": "61d 21h 52m 38s", "attempt": "1/1", "status_info": "OK - user: 0\\.6%, system: 0\\.3%, wait: 0\\.4%", - "event_type": "CREATE|UPDATE", "sync_mode": "snapshot|init_snapshot|update" } \ No newline at end of file diff --git a/vitrage/tests/resources/sync_nagios_snapshot_static.json b/vitrage/tests/resources/sync_nagios_snapshot_static.json index 25f7a3f19..ddf522f18 100644 --- a/vitrage/tests/resources/sync_nagios_snapshot_static.json +++ b/vitrage/tests/resources/sync_nagios_snapshot_static.json @@ -1,4 +1,3 @@ { - "status": "OK", - "event_type": "DELETE" + "status": "OK" } \ No newline at end of file diff --git a/vitrage/tests/unit/synchronizer/nagios/nagios_base_test.py b/vitrage/tests/unit/synchronizer/nagios/nagios_base_test.py new file mode 100644 index 000000000..cdbe1f404 --- /dev/null +++ b/vitrage/tests/unit/synchronizer/nagios/nagios_base_test.py @@ -0,0 +1,37 @@ +# Copyright 2016 - Nokia +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from vitrage.synchronizer.plugins.nagios.properties import NagiosProperties \ + as NagiosProps +from vitrage.tests import base + + +class NagiosBaseTest(base.BaseTest): + def _assert_contains(self, expected_service, services): + for service in services: + if service[NagiosProps.RESOURCE_NAME] == \ + expected_service[NagiosProps.RESOURCE_NAME] and \ + service[NagiosProps.SERVICE] == \ + expected_service[NagiosProps.SERVICE]: + self._assert_expected_service(expected_service, service) + return + + self.fail("service not found: %(resource_name)s %(service_name)s" % + {'resource_name': + expected_service[NagiosProps.RESOURCE_NAME], + 'service_name': + expected_service[NagiosProps.SERVICE]}) + + def _assert_expected_service(self, expected_service, service): + for key, value in expected_service.items(): + self.assertEqual(value, service[key], 'wrong value for ' + key) diff --git a/vitrage/tests/unit/synchronizer/nagios/synchronizer_with_mock_data.py b/vitrage/tests/unit/synchronizer/nagios/synchronizer_with_mock_data.py new file mode 100644 index 000000000..0fa06b0aa --- /dev/null +++ b/vitrage/tests/unit/synchronizer/nagios/synchronizer_with_mock_data.py @@ -0,0 +1,43 @@ +# Copyright 2016 - Nokia +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from vitrage.synchronizer.plugins.nagios.synchronizer import NagiosSynchronizer +from vitrage.tests.mocks import mock_syncronizer as mock_sync + + +class NagiosSynchronizerWithMockData(NagiosSynchronizer): + """A nagios synchronizer for tests. + + Instead of calling Nagios URL to get the data, it returns the data it + is asked to + """ + + def __init__(self, conf): + super(NagiosSynchronizerWithMockData, self).__init__(conf) + self.service_datas = None + + def set_service_datas(self, service_datas): + self.service_datas = service_datas + + def _get_services_from_nagios(self): + alarms = [] + for service_data in self.service_datas: + generators = mock_sync.simple_nagios_alarm_generators( + host_num=1, + events_num=1, + snap_vals=service_data) + alarms.append( + mock_sync.generate_sequential_events_list(generators)[0]) + + return alarms diff --git a/vitrage/tests/unit/synchronizer/nagios/test_nagios_parser.py b/vitrage/tests/unit/synchronizer/nagios/test_nagios_parser.py index 518bb733c..bd358670b 100644 --- a/vitrage/tests/unit/synchronizer/nagios/test_nagios_parser.py +++ b/vitrage/tests/unit/synchronizer/nagios/test_nagios_parser.py @@ -15,13 +15,14 @@ from oslo_log import log as logging from vitrage.synchronizer.plugins.nagios.parser import NagiosParser from vitrage.synchronizer.plugins.nagios.properties import NagiosProperties -from vitrage.tests import base from vitrage.tests.mocks import utils +from vitrage.tests.unit.synchronizer.nagios.nagios_base_test \ + import NagiosBaseTest LOG = logging.getLogger(__name__) -class NagiosParserTest(base.BaseTest): +class NagiosParserTest(NagiosBaseTest): expected_service1 = {NagiosProperties.RESOURCE_NAME: 'compute-0-0.local', NagiosProperties.SERVICE: 'CPU load', @@ -64,25 +65,6 @@ class NagiosParserTest(base.BaseTest): # Test assertions self.assertTrue(nagios_services) - self._assert_contains(nagios_services, self.expected_service1) - self._assert_contains(nagios_services, self.expected_service2) - self._assert_contains(nagios_services, self.expected_service3) - - def _assert_contains(self, services, expected_service): - for service in services: - if service[NagiosProperties.RESOURCE_NAME] == \ - expected_service[NagiosProperties.RESOURCE_NAME] and \ - service[NagiosProperties.SERVICE] == \ - expected_service[NagiosProperties.SERVICE]: - self._assert_expected_service(expected_service, service) - return - - self.fail("service not found: %(resource_name)s %(service_name)s" % - {'resource_name': - expected_service[NagiosProperties.RESOURCE_NAME], - 'service_name': - expected_service[NagiosProperties.SERVICE]}) - - def _assert_expected_service(self, expected_service, service): - for key, value in expected_service.items(): - self.assertEqual(value, service[key], 'wrong value for ' + key) + self._assert_contains(self.expected_service1, nagios_services) + self._assert_contains(self.expected_service2, nagios_services) + self._assert_contains(self.expected_service3, nagios_services) diff --git a/vitrage/tests/unit/synchronizer/nagios/test_nagios_synchronizer.py b/vitrage/tests/unit/synchronizer/nagios/test_nagios_synchronizer.py new file mode 100644 index 000000000..8568ec9d5 --- /dev/null +++ b/vitrage/tests/unit/synchronizer/nagios/test_nagios_synchronizer.py @@ -0,0 +1,142 @@ +# Copyright 2016 - Nokia +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from oslo_config import cfg +from oslo_log import log as logging + +from vitrage.synchronizer.plugins.nagios.properties import NagiosProperties \ + as NagiosProps +from vitrage.tests.unit.synchronizer.nagios.nagios_base_test \ + import NagiosBaseTest +from vitrage.tests.unit.synchronizer.nagios.synchronizer_with_mock_data \ + import NagiosSynchronizerWithMockData + +LOG = logging.getLogger(__name__) + + +class NagiosSynchronizerTest(NagiosBaseTest): + + def setUp(self): + super(NagiosSynchronizerTest, self).setUp() + self.conf = cfg.ConfigOpts() + + def test_get_all(self): + """Check get_all functionality. + + Check the logic of which tests are returned: tests that are not OK, + or tests that were changed from not-OK to OK + """ + + # Setup + nagios_synchronizer = NagiosSynchronizerWithMockData(self.conf) + + # Action + service_data1 = {NagiosProps.RESOURCE_NAME: 'compute-0', + NagiosProps.SERVICE: 'CPU utilization', + NagiosProps.STATUS: 'OK'} + service_data2 = {NagiosProps.RESOURCE_NAME: 'compute-1', + NagiosProps.SERVICE: 'CPU utilization', + NagiosProps.STATUS: 'OK'} + service_data3 = {NagiosProps.RESOURCE_NAME: 'compute-1', + NagiosProps.SERVICE: 'Uptime', + NagiosProps.STATUS: 'OK'} + + nagios_synchronizer.set_service_datas([service_data1, + service_data2, + service_data3]) + + services = nagios_synchronizer._get_services() + + # Test assertions + # Services with status OK should not be returned + self.assertIsNotNone(services, 'No services returned') + self.assertEqual(0, len(services)) + + # Action + service_data1 = {NagiosProps.RESOURCE_NAME: 'compute-0', + NagiosProps.SERVICE: 'CPU utilization', + NagiosProps.STATUS: 'WARNING'} + service_data2 = {NagiosProps.RESOURCE_NAME: 'compute-1', + NagiosProps.SERVICE: 'CPU utilization', + NagiosProps.STATUS: 'OK'} + service_data3 = {NagiosProps.RESOURCE_NAME: 'compute-1', + NagiosProps.SERVICE: 'Uptime', + NagiosProps.STATUS: 'OK'} + + nagios_synchronizer.set_service_datas([service_data1, + service_data2, + service_data3]) + + services = nagios_synchronizer._get_services() + + # Test assertions + self.assertIsNotNone(services, 'No services returned') + self.assertEqual(1, len(services)) + self._assert_contains(service_data1, services) + + # Action + service_data1 = {NagiosProps.RESOURCE_NAME: 'compute-0', + NagiosProps.SERVICE: 'CPU utilization', + NagiosProps.STATUS: 'CRITICAL'} + service_data2 = {NagiosProps.RESOURCE_NAME: 'compute-1', + NagiosProps.SERVICE: 'CPU utilization', + NagiosProps.STATUS: 'WARNING'} + service_data3 = {NagiosProps.RESOURCE_NAME: 'compute-1', + NagiosProps.SERVICE: 'Uptime', + NagiosProps.STATUS: 'OK'} + + nagios_synchronizer.set_service_datas([service_data1, + service_data2, + service_data3]) + + services = nagios_synchronizer._get_services() + + # Test assertions + self.assertIsNotNone(services, 'No services returned') + self.assertEqual(2, len(services)) + self._assert_contains(service_data1, services) + self._assert_contains(service_data2, services) + + # Action + service_data1 = {NagiosProps.RESOURCE_NAME: 'compute-0', + NagiosProps.SERVICE: 'CPU utilization', + NagiosProps.STATUS: 'OK'} + service_data2 = {NagiosProps.RESOURCE_NAME: 'compute-1', + NagiosProps.SERVICE: 'CPU utilization', + NagiosProps.STATUS: 'OK'} + service_data3 = {NagiosProps.RESOURCE_NAME: 'compute-1', + NagiosProps.SERVICE: 'Uptime', + NagiosProps.STATUS: 'OK'} + + nagios_synchronizer.set_service_datas([service_data1, + service_data2, + service_data3]) + + services = nagios_synchronizer._get_services() + + # Test assertions + # The services of service_data1/2 should be returned although their + # status is OK, because they were not OK earlier + self.assertIsNotNone(services, 'No services returned') + self.assertEqual(2, len(services)) + self._assert_contains(service_data1, services) + self._assert_contains(service_data2, services) + + # Action + services = nagios_synchronizer._get_services() + + # Test assertions + # Calling get_services again should not return anything, since all + # services are still OK + self.assertIsNotNone(services, 'services is None') + self.assertEqual(0, len(services))