From 004b3e1bda195af48770a09607cfe0c4aa5f4c40 Mon Sep 17 00:00:00 2001 From: "Paul A. Jungwirth" Date: Thu, 10 Aug 2017 22:04:15 +0000 Subject: [PATCH 1/3] Added method to ask for active checks --- pyzabbix/__init__.py | 2 +- pyzabbix/sender.py | 115 +++++++++++++++++++++++++++++++++++++++++++ tests/test_sender.py | 42 +++++++++++++++- 3 files changed, 157 insertions(+), 2 deletions(-) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index eabd5d1..89eb04f 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -1,4 +1,4 @@ from .api import ZabbixAPI, ZabbixAPIException, ssl_context_compat -from .sender import ZabbixMetric, ZabbixSender, ZabbixResponse +from .sender import ZabbixMetric, ZabbixSender, ZabbixResponse, ZabbixActiveChecksResponse, ZabbixCheck __version__ = '1.1.3' diff --git a/pyzabbix/sender.py b/pyzabbix/sender.py index 196f073..c3524a6 100644 --- a/pyzabbix/sender.py +++ b/pyzabbix/sender.py @@ -131,6 +131,66 @@ def __repr__(self): return result +class ZabbixActiveChecksResponse(object): + """The :class:`ZabbixActiveChecksResponse` contains the parsed response from Zabbix. + """ + def __init__(self): + self.checks = None + + def __repr__(self): + """Represent detailed ZabbixActiveChecksResponse view.""" + if self.checks is None: + return '' + else: + return "[%s]" % ", ".join([ch.__repr__() for ch in self.checks]) + + def parse(self, response): + """Parse zabbix response for active checks.""" + self.checks = [] + data = response.get('data') + for o in data: + ch = ZabbixCheck( + o.get('key'), + o.get('delay'), + o.get('lastlogsize'), # optional in 2.2; required in 2.4, 3.0, 3.2, 3.4 + o.get('mtime')) # unused in 3.2; required in 2.4, 3.0, 3.2, 3.4 + self.checks.append(ch) + + +class ZabbixCheck(object): + """The :class:`ZabbixCheck` contains one active check the Zabbix server would like. + + :type key: str + :param key: Key by which you will identify this metric. + + :type delay: int + :param delay: Period (in seconds) to collect this metric. + + :type lastlogsize: int + :param lastlogsize: Point in the log file already known by the server. + + :type mtime: int + :param mtime: Last time the server heard about this metric. + + >>> from pyzabbix import ZabbixCheck + >>> ZabbixCheck('cpu[usage]', 60, 0, 0) + """ + + def __init__(self, key, delay, lastlogsize, mtime): + self.key = str(key) + self.delay = delay + self.lastlogsize = lastlogsize + self.mtime = mtime + + def __repr__(self): + """Represent detailed ZabbixCheck view.""" + + result = json.dumps(self.__dict__) + logger.debug('%s: %s', self.__class__.__name__, result) + + return result + + class ZabbixSender(object): """The :class:`ZabbixSender` send metrics to Zabbix server. @@ -385,3 +445,58 @@ def send(self, metrics): for m in range(0, len(metrics), self.chunk_size): result.parse(self._chunk_send(metrics[m:m + self.chunk_size])) return result + + def _create_active_checks_request(self, host): + """Create a formatted request to zabbix from a host name. + + :type host: str + :param host: The host to ask about + + :rtype: str + :return: Formatted zabbix request + """ + request = '{"request":"active checks","host":"%s"}' % host + request = request.encode("utf-8") + logger.debug('Request: %s', request) + return request + + def send_active_checks(self, host): + """Get active checks from the zabbix server. + + :type host: str + :param host: host to ask about + + :rtype: :class:`pyzabbix.sender.ZabbixActiveChecksResponse` + :return: Parsed response from Zabbix Server + """ + request = self._create_active_checks_request(host) + packet = self._create_packet(request) + + for host_addr in self.zabbix_uri: + logger.debug('Sending data to %s', host_addr) + + # create socket object + connection = socket.socket() + + # server and port must be tuple + connection.connect(host_addr) + + try: + connection.sendall(packet) + except Exception as err: + # In case of error we should close connection, + # otherwise we will close it after data will be received. + connection.close() + raise Exception(err) + + response = self._get_response(connection) + logger.debug('%s response: %s', host_addr, response) + + if response and response.get('response') != 'success': + logger.debug('Response error: %s}', response) + raise Exception(response) + + result = ZabbixActiveChecksResponse() + result.parse(response) + return result + diff --git a/tests/test_sender.py b/tests/test_sender.py index 32962ba..c7a08aa 100644 --- a/tests/test_sender.py +++ b/tests/test_sender.py @@ -12,7 +12,7 @@ from unittest.mock import patch, call, mock_open autospec = True -from pyzabbix import ZabbixMetric, ZabbixSender, ZabbixResponse +from pyzabbix import ZabbixMetric, ZabbixSender, ZabbixResponse, ZabbixActiveChecksResponse, ZabbixCheck class TestZabbixResponse(TestCase): @@ -51,11 +51,41 @@ def test_repr(self): self.assertEqual(zm_repr, zm.__dict__) +class TestZabbixActiveChecksResponse(TestCase): + def test_init(self): + zr = ZabbixActiveChecksResponse() + self.assertIsNone(zr.checks) + + def test_repr(self): + zr = ZabbixActiveChecksResponse() + self.assertEqual(zr.__repr__(), "") + + def test_parse(self): + zr = ZabbixActiveChecksResponse() + zr.parse({'data': [ + {'key':'cpu[usage]', 'delay':60, 'lastlogsize':5, 'mtime':9}, + {'key':'disk[io]', 'delay':120, 'lastlogsize':8, 'mtime':4}]}) + self.assertEqual(zr.checks[0].key, "cpu[usage]") + self.assertEqual(zr.checks[0].delay, 60) + self.assertEqual(zr.checks[0].lastlogsize, 5) + self.assertEqual(zr.checks[0].mtime, 9) + self.assertEqual(zr.checks[1].key, "disk[io]") + self.assertEqual(zr.checks[1].delay, 120) + self.assertEqual(zr.checks[1].lastlogsize, 8) + self.assertEqual(zr.checks[1].mtime, 4) + + class TestsZabbixSender(TestCase): def setUp(self): self.resp_header = b'ZBXD\x01\\\x00\x00\x00\x00\x00\x00\x00' self.resp_body = b'''{"response":"success","info":"processed: 0; \ failed: 10; total: 10; seconds spent: 0.000078"} +''' + + self.resp_header_active_checks = b'ZBXD\x01\\\x00\x00\x00\x00\x00\x00\x00' + self.resp_body_active_checks = b'''{"response":"success","data":[\ +{"key":"cpu[usage]","delay":60,"lastlogsize":6,"mtime":4},\ +{"key":"disk[io]","delay":1200,"lastlogsize":9,"mtime":7}]} ''' def test_init(self): @@ -185,6 +215,16 @@ def test_get_response_fail_s_close(self, mock_socket): result = zs._get_response(mock_socket) self.assertFalse(result) + @patch('pyzabbix.sender.socket.socket', autospec=autospec) + def test_get_response_to_active_checks(self, mock_socket): + mock_socket.recv.side_effect = (self.resp_header_active_checks, self.resp_body_active_checks) + + zs = ZabbixSender() + result = zs._get_response(mock_socket) + mock_socket.recv.assert_has_calls([call(92)]) + self.assertEqual(result['response'], 'success') + self.assertIsNotNone(result['data']) + @patch('pyzabbix.sender.socket.socket', autospec=autospec) def test_send(self, mock_socket): mock_data = b'\x01\\\x00\x00\x00\x00\x00\x00\x00' From ab81c7bfc629dab73b7cd961b0326b1a1471da06 Mon Sep 17 00:00:00 2001 From: "Paul A. Jungwirth" Date: Fri, 11 Aug 2017 22:20:59 +0000 Subject: [PATCH 2/3] pep8 fixes --- pyzabbix/__init__.py | 3 ++- pyzabbix/sender.py | 13 ++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/pyzabbix/__init__.py b/pyzabbix/__init__.py index 89eb04f..883040b 100644 --- a/pyzabbix/__init__.py +++ b/pyzabbix/__init__.py @@ -1,4 +1,5 @@ from .api import ZabbixAPI, ZabbixAPIException, ssl_context_compat -from .sender import ZabbixMetric, ZabbixSender, ZabbixResponse, ZabbixActiveChecksResponse, ZabbixCheck +from .sender import (ZabbixMetric, ZabbixSender, ZabbixResponse, + ZabbixActiveChecksResponse, ZabbixCheck) __version__ = '1.1.3' diff --git a/pyzabbix/sender.py b/pyzabbix/sender.py index c3524a6..1aeb208 100644 --- a/pyzabbix/sender.py +++ b/pyzabbix/sender.py @@ -132,7 +132,8 @@ def __repr__(self): class ZabbixActiveChecksResponse(object): - """The :class:`ZabbixActiveChecksResponse` contains the parsed response from Zabbix. + """The :class:`ZabbixActiveChecksResponse` + contains the parsed response from Zabbix. """ def __init__(self): self.checks = None @@ -149,16 +150,19 @@ def parse(self, response): self.checks = [] data = response.get('data') for o in data: + # lastlogsize is optional in 2.2; required in 2.4, 3.0, 3.2, 3.4 + # mtime is unused in 3.2; required in 2.4, 3.0, 3.2, 3.4 ch = ZabbixCheck( o.get('key'), o.get('delay'), - o.get('lastlogsize'), # optional in 2.2; required in 2.4, 3.0, 3.2, 3.4 - o.get('mtime')) # unused in 3.2; required in 2.4, 3.0, 3.2, 3.4 + o.get('lastlogsize'), + o.get('mtime')) self.checks.append(ch) class ZabbixCheck(object): - """The :class:`ZabbixCheck` contains one active check the Zabbix server would like. + """The :class:`ZabbixCheck` contains one active check + the Zabbix server would like. :type key: str :param key: Key by which you will identify this metric. @@ -499,4 +503,3 @@ def send_active_checks(self, host): result = ZabbixActiveChecksResponse() result.parse(response) return result - From 60e26dbb235886b3e051da60172cc4a1c759ae8e Mon Sep 17 00:00:00 2001 From: "Paul A. Jungwirth" Date: Fri, 11 Aug 2017 23:08:32 +0000 Subject: [PATCH 3/3] pep8 the tests too --- tests/test_sender.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/test_sender.py b/tests/test_sender.py index c7a08aa..64cada2 100644 --- a/tests/test_sender.py +++ b/tests/test_sender.py @@ -12,7 +12,8 @@ from unittest.mock import patch, call, mock_open autospec = True -from pyzabbix import ZabbixMetric, ZabbixSender, ZabbixResponse, ZabbixActiveChecksResponse, ZabbixCheck +from pyzabbix import (ZabbixMetric, ZabbixSender, ZabbixResponse, + ZabbixActiveChecksResponse, ZabbixCheck) class TestZabbixResponse(TestCase): @@ -63,8 +64,8 @@ def test_repr(self): def test_parse(self): zr = ZabbixActiveChecksResponse() zr.parse({'data': [ - {'key':'cpu[usage]', 'delay':60, 'lastlogsize':5, 'mtime':9}, - {'key':'disk[io]', 'delay':120, 'lastlogsize':8, 'mtime':4}]}) + {'key': 'cpu[usage]', 'delay': 60, 'lastlogsize': 5, 'mtime': 9}, + {'key': 'disk[io]', 'delay': 120, 'lastlogsize': 8, 'mtime': 4}]}) self.assertEqual(zr.checks[0].key, "cpu[usage]") self.assertEqual(zr.checks[0].delay, 60) self.assertEqual(zr.checks[0].lastlogsize, 5) @@ -82,7 +83,8 @@ def setUp(self): failed: 10; total: 10; seconds spent: 0.000078"} ''' - self.resp_header_active_checks = b'ZBXD\x01\\\x00\x00\x00\x00\x00\x00\x00' + self.resp_header_active_checks = \ + b'ZBXD\x01\\\x00\x00\x00\x00\x00\x00\x00' self.resp_body_active_checks = b'''{"response":"success","data":[\ {"key":"cpu[usage]","delay":60,"lastlogsize":6,"mtime":4},\ {"key":"disk[io]","delay":1200,"lastlogsize":9,"mtime":7}]} @@ -217,7 +219,10 @@ def test_get_response_fail_s_close(self, mock_socket): @patch('pyzabbix.sender.socket.socket', autospec=autospec) def test_get_response_to_active_checks(self, mock_socket): - mock_socket.recv.side_effect = (self.resp_header_active_checks, self.resp_body_active_checks) + mock_socket.recv.side_effect = ( + self.resp_header_active_checks, + self.resp_body_active_checks + ) zs = ZabbixSender() result = zs._get_response(mock_socket)