Skip to content

Added method to ask for active checks #72

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion pyzabbix/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
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'
118 changes: 118 additions & 0 deletions pyzabbix/sender.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,70 @@ 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])
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better represent it as json. Which you doing in ZabbixCheck.__repr__. I think it should be consisted.


def parse(self, response):
"""Parse zabbix response for active checks."""
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'),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If some keys are required and not optional, we should get them explicitly to raise exception on error. Otherwise we may get empty checks on error.

o.get('delay'),
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.

: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
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's usefull decorate properties as actually properties.
Example:

     @property
     def chunk(self):
         return self._chunk


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.

Expand Down Expand Up @@ -385,3 +449,57 @@ 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
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better use format method here.
But actually, I don't think we need this as separate function, because it's just 2-3 lines of code and it used just in one place. I think it should go to send_active_checks

request = request.encode("utf-8")
logger.debug('Request: %s', request)
return request

def send_active_checks(self, host):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function returns list of active checks from zabbix server, then why it calls send_active_checks instead of, an example, get_active_checks?

"""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:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this part must be refactored as a function _send because you duplicating big chunk code of _chunk_send here.

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
47 changes: 46 additions & 1 deletion tests/test_sender.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
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):
Expand Down Expand Up @@ -51,11 +52,42 @@ 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):
Expand Down Expand Up @@ -185,6 +217,19 @@ 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'
Expand Down