From 9475fbcd4d192de45210d31d49f050625a80b37c Mon Sep 17 00:00:00 2001 From: Vsevolod Novikov Date: Wed, 2 Mar 2016 17:59:21 +0300 Subject: [PATCH 01/17] Installer has been added --- setup.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100755 setup.py diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..464ebea --- /dev/null +++ b/setup.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python + +import os.path +from setuptools import setup, find_packages + +def read(fname): + return open(os.path.join(os.path.dirname(__file__), fname)).read() + +setupconf = dict( + name="pyZabbixSender", + py_modules=["pyZabbixSender"], + author="Kurt Momberg", + author_email="kurtqm@yahoo.com.ar", + description="Python implementation of zabbix_sender.", + long_description = read('README.md'), + url="https://github.com/kmomberg/pyZabbixSender", + version="0.1", + license = "GNU GPL v2", +# packages = find_packages(), +# install_requires = [???], + classifiers = [ + "Operating System :: OS Independent", + "Development Status :: 4 - Beta", + "Environment :: Library", + "Framework :: Zabbix", + "Intended Audience :: Developers", + "Programming Language :: Python", + "Programming Language :: Python :: 2.5.1", + "Programming Language :: Python :: 2.7", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: System :: Networking", + "Topic :: System :: Monitoring", + ], + zip_safe=False, +) + +if __name__ == '__main__': + setup(**setupconf) From 6dbab73e2b8e17b29620ce83894d087d62cc572d Mon Sep 17 00:00:00 2001 From: Vsevolod Novikov Date: Wed, 9 Mar 2016 16:07:04 +0300 Subject: [PATCH 02/17] Some verbosity --- pyZabbixSender.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyZabbixSender.py b/pyZabbixSender.py index 2588f2a..9836371 100644 --- a/pyZabbixSender.py +++ b/pyZabbixSender.py @@ -95,7 +95,7 @@ def __send(self, mydata): response_header = sock.recv(5) if not response_header == 'ZBXD\1': - err_message = u'Invalid response from server. Malformed data?\n---\n%s\n---\n' % str(mydata) + err_message = u'Invalid response from server [%s]. Malformed data?\n---\n%s\n---\n' % (repr(response_header),str(mydata)) sys.stderr.write(err_message) return self.RC_ERR_INV_RESP, err_message From 949c48ea2f20b2b38b08b38ed9f1ed07fc8eb2a5 Mon Sep 17 00:00:00 2001 From: Vsevolod Novikov Date: Wed, 9 Mar 2016 17:08:41 +0300 Subject: [PATCH 03/17] Separate network protocol from the base data --- pyZabbixSender.py | 171 ++++-------------------------------------- pyZabbixSenderBase.py | 156 ++++++++++++++++++++++++++++++++++++++ setup.py | 5 +- 3 files changed, 176 insertions(+), 156 deletions(-) create mode 100644 pyZabbixSenderBase.py diff --git a/pyZabbixSender.py b/pyZabbixSender.py index 9836371..bc0011c 100644 --- a/pyZabbixSender.py +++ b/pyZabbixSender.py @@ -16,14 +16,13 @@ #import simplejson as json import json +from pyZabbixSenderBase import pyZabbixSenderBase -class pyZabbixSender: +class pyZabbixSender(pyZabbixSenderBase): ''' This class allows you to send data to a Zabbix server, using the same protocol used by the zabbix_server binary distributed by Zabbix. ''' - ZABBIX_SERVER = "127.0.0.1" - ZABBIX_PORT = 10051 # Return codes when sending data: RC_OK = 0 # Everything ok @@ -32,50 +31,6 @@ class pyZabbixSender: RC_ERR_CONN = 255 # Error talking to the server RC_ERR_INV_RESP = 254 # Invalid response from server - - def __init__(self, server=ZABBIX_SERVER, port=ZABBIX_PORT, verbose=False): - ''' - #####Description: - This is the constructor, to obtain an object of type pyZabbixSender, linked to work with a specific server/port. - - #####Parameters: - * **server**: [in] [string] [optional] This is the server domain name or IP. *Default value: "127.0.0.1"* - * **port**: [in] [integer] [optional] This is the port open in the server to receive zabbix traps. *Default value: 10051* - * **verbose**: [in] [boolean] [optional] This is to allow the library to write some output to stderr when finds an error. *Default value: False* - - **Note: The "verbose" parameter will be revisited and could be removed/replaced in the future** - - #####Return: - It returns a pyZabbixSender object. - ''' - self.zserver = server - self.zport = port - self.verbose = verbose - self.timeout = 5 # Socket connection timeout. - self.__data = [] # This is to store data to be sent later. - - - def __str__(self): - ''' - This allows you to obtain a string representation of the internal data - ''' - return str(self.__data) - - - def __createDataPoint(self, host, key, value, clock=None): - ''' - Creates a dictionary using provided parameters, as needed for sending this data. - ''' - obj = { - 'host': host, - 'key': key, - 'value': value, - } - if clock: - obj['clock'] = clock - return obj - - def __send(self, mydata): ''' This is the method that actually sends the data to the zabbix server. @@ -119,99 +74,6 @@ def __send(self, mydata): return self.RC_ERR_FAIL_SEND, response return self.RC_OK, response - - def addData(self, host, key, value, clock=None): - ''' - #####Description: - Adds host, key, value and optionally clock to the internal list of data to be sent later, when calling one of the methods to actually send the data to the server. - - #####Parameters: - * **host**: [in] [string] [mandatory] The host which the data is associated to. - * **key**: [in] [string] [mandatory] The name of the trap associated to the host in the Zabbix server. - * **value**: [in] [any] [mandatory] The value you want to send. Please note that you need to take care about the type, as it needs to match key definition in the Zabbix server. Numeric types can be specified as number (for example: 12) or text (for example: "12"). - * **clock**: [in] [integer] [optional] Here you can specify the Unix timestamp associated to your measurement. For example, you can process a log or a data file produced an hour ago, and you want to send the data with the timestamp when the data was produced, not when it was processed by you. If you don't specify this parameter, zabbix server will assign a timestamp when it receives the data. - - You can create a timestamp compatible with "clock" parameter using this code: - int(round(time.time())) - - *Default value: None* - - #####Return: - This method doesn't have a return. - ''' - obj = self.__createDataPoint(host, key, value, clock) - self.__data.append(obj) - - - def clearData(self): - ''' - #####Description: - This method removes all data from internal storage. You need to specify when it's done, as it's not automatically done after a data send operation. - - #####Parameters: - None - - #####Return: - None - ''' - self.__data = [] - - - def getData(self): - ''' - #####Description: - This method is used to obtain a copy of the internal data stored in the object. - - Please note you will **NOT** get the internal data object, but a copy of it, so no matter what you do with your copy, internal data will remain safe. - - #####Parameters: - None - - #####Return: - A copy of the internal data you added using the method *addData* (an array of dicts). - ''' - copy_of_data = [] - for data_point in self.__data: - copy_of_data.append(data_point.copy()) - return copy_of_data - - - def printData(self): - ''' - #####Description: - Print stored data (to stdout), so you can see what will be sent if "sendData" is called. This is useful for debugging purposes. - - #####Parameters: - None - - #####Return: - None - ''' - for elem in self.__data: - print str(elem) - print 'Count: %d' % len(self.__data) - - - def removeDataPoint(self, data_point): - ''' - #####Description: - This method delete one data point from the internal stored data. - - It's main purpose is to narrow the internal data to keep only those failed data points (those that were not received/processed by the server) so you can identify/retry them. Data points can be obtained from *sendDataOneByOne* return, or from *getData* return. - - #####Parameters: - * **data_point**: [in] [dict] [mandatory] This is a dictionary as returned by *sendDataOneByOne()* or *getData* methods. - - #####Return: - It returns True if data_point was found and deleted, and False if not. - ''' - if data_point in self.__data: - self.__data.remove(data_point) - return True - - return False - - def sendData(self, packet_clock=None, max_data_per_conn=None): ''' #####Description: @@ -226,24 +88,24 @@ def sendData(self, packet_clock=None, max_data_per_conn=None): int(round(time.time())) *Default value: None* - + * **max_data_per_conn**: [in] [integer] [optional] Allows the user to limit the number of data points sent in one single connection, as some times a too big number can produce problems over slow connections. Several "sends" will be automatically performed until all data is sent. If omitted, all data points will be sent in one single connection. *Default value: None* - + Please note that **internal data is not deleted after *sendData* is executed**. You need to call *clearData* after sending it, if you want to remove currently stored data. #####Return: A list of *(return_code, msg_from_server)* associated to each "send" operation. ''' - if not max_data_per_conn or max_data_per_conn > len(self.__data): - max_data_per_conn = len(self.__data) + if not max_data_per_conn or max_data_per_conn > len(self._data): + max_data_per_conn = len(self._data) responses = [] i = 0 - while i*max_data_per_conn < len(self.__data): + while i*max_data_per_conn < len(self._data): sender_data = { "request": "sender data", @@ -252,7 +114,7 @@ def sendData(self, packet_clock=None, max_data_per_conn=None): if packet_clock: sender_data['clock'] = packet_clock - sender_data['data'] = self.__data[i*max_data_per_conn:(i+1)*max_data_per_conn] + sender_data['data'] = self._data[i*max_data_per_conn:(i+1)*max_data_per_conn] to_send = json.dumps(sender_data) response = self.__send(to_send) @@ -280,7 +142,7 @@ def sendDataOneByOne(self): It returns an array of return codes (one for each individual "send") and the data sent: \[\[code\_1, data\_point\_1], \[code\_2, data\_point\_2\]\] ''' retarray = [] - for i in self.__data: + for i in self._data: if 'clock' in i: (retcode, retstring) = self.sendSingle(i['host'], i['key'], i['value'], i['clock']) else: @@ -304,7 +166,7 @@ def sendSingle(self, host, key, value, clock=None): You can create a timestamp compatible with "clock" parameter using this code: int(round(time.time())) - + *Default value: None* #####Return: @@ -315,7 +177,7 @@ def sendSingle(self, host, key, value, clock=None): "data": [], } - obj = self.__createDataPoint(host, key, value, clock) + obj = self._createDataPoint(host, key, value, clock) sender_data['data'].append(obj) to_send = json.dumps(sender_data) return self.__send(to_send) @@ -326,7 +188,7 @@ def sendSingleLikeProxy(self, host, key, value, clock=None, proxy=None): #####Description: Use this method to put the data for host monitored by proxy server. This method emulates proxy protocol and data will be accepted by Zabbix server even if they were send not actually from proxy. - + #####Parameters: * **host**: [in] [string] [mandatory] The host which the data is associated to. * **key**: [in] [string] [mandatory] The name of the trap associated to the host in the Zabbix server. @@ -335,29 +197,28 @@ def sendSingleLikeProxy(self, host, key, value, clock=None, proxy=None): You can create a timestamp compatible with "clock" parameter using this code: int(round(time.time())) - + *Default value: None* + * **proxy**: [in] [string] [optional] The name of the proxy to be recognized by the Zabbix server. If proxy is not specified, a normal "sendSingle" operation will be performed. *Default value: None* - #####Return: A list containing the return code and the message returned by the server. ''' # Proxy was not specified, so we'll do a "normal" sendSingle operation if proxy is None: return sendSingle(host, key, value, clock) - + sender_data = { "request": "history data", "host": proxy, "data": [], } - obj = self.__createDataPoint(host, key, value, clock) + obj = self._createDataPoint(host, key, value, clock) sender_data['data'].append(obj) to_send = json.dumps(sender_data) return self.__send(to_send) - ##################################### # --- Examples of usage --- ##################################### diff --git a/pyZabbixSenderBase.py b/pyZabbixSenderBase.py new file mode 100644 index 0000000..50c0e05 --- /dev/null +++ b/pyZabbixSenderBase.py @@ -0,0 +1,156 @@ +# -*- coding: utf-8 +# Copyright 2015 Kurt Momberg +# > Based on work by Klimenko Artyem +# >> Based on work by Rob Cherry +# >>> Based on work by Enrico Tröger +# License: GNU GPLv2 + +import struct +import time +import sys +import re + +# If you're using an old version of python that don't have json available, +# you can use simplejson instead: https://simplejson.readthedocs.org/en/latest/ +#import simplejson as json +import json + +class pyZabbixSenderBase: + ''' + This class creates network-agnostic data structures to send data to a Zabbix server + ''' + ZABBIX_SERVER = "127.0.0.1" + ZABBIX_PORT = 10051 + + def __init__(self, server=ZABBIX_SERVER, port=ZABBIX_PORT, verbose=False): + ''' + #####Description: + This is the constructor, to obtain an object of type pyZabbixSender, linked to work with a specific server/port. + + #####Parameters: + * **server**: [in] [string] [optional] This is the server domain name or IP. *Default value: "127.0.0.1"* + * **port**: [in] [integer] [optional] This is the port open in the server to receive zabbix traps. *Default value: 10051* + * **verbose**: [in] [boolean] [optional] This is to allow the library to write some output to stderr when finds an error. *Default value: False* + + **Note: The "verbose" parameter will be revisited and could be removed/replaced in the future** + + #####Return: + It returns a pyZabbixSender object. + ''' + self.zserver = server + self.zport = port + self.verbose = verbose + self.timeout = 5 # Socket connection timeout. + self._data = [] # This is to store data to be sent later. + + + def __str__(self): + ''' + This allows you to obtain a string representation of the internal data + ''' + return str(self._data) + + + def _createDataPoint(self, host, key, value, clock=None): + ''' + Creates a dictionary using provided parameters, as needed for sending this data. + ''' + obj = { + 'host': host, + 'key': key, + 'value': value, + } + if clock: + obj['clock'] = clock + return obj + + def addData(self, host, key, value, clock=None): + ''' + #####Description: + Adds host, key, value and optionally clock to the internal list of data to be sent later, when calling one of the methods to actually send the data to the server. + + #####Parameters: + * **host**: [in] [string] [mandatory] The host which the data is associated to. + * **key**: [in] [string] [mandatory] The name of the trap associated to the host in the Zabbix server. + * **value**: [in] [any] [mandatory] The value you want to send. Please note that you need to take care about the type, as it needs to match key definition in the Zabbix server. Numeric types can be specified as number (for example: 12) or text (for example: "12"). + * **clock**: [in] [integer] [optional] Here you can specify the Unix timestamp associated to your measurement. For example, you can process a log or a data file produced an hour ago, and you want to send the data with the timestamp when the data was produced, not when it was processed by you. If you don't specify this parameter, zabbix server will assign a timestamp when it receives the data. + + You can create a timestamp compatible with "clock" parameter using this code: + int(round(time.time())) + + *Default value: None* + + #####Return: + This method doesn't have a return. + ''' + obj = self._createDataPoint(host, key, value, clock) + self._data.append(obj) + + + def clearData(self): + ''' + #####Description: + This method removes all data from internal storage. You need to specify when it's done, as it's not automatically done after a data send operation. + + #####Parameters: + None + + #####Return: + None + ''' + self._data = [] + + + def getData(self): + ''' + #####Description: + This method is used to obtain a copy of the internal data stored in the object. + + Please note you will **NOT** get the internal data object, but a copy of it, so no matter what you do with your copy, internal data will remain safe. + + #####Parameters: + None + + #####Return: + A copy of the internal data you added using the method *addData* (an array of dicts). + ''' + copy_of_data = [] + for data_point in self._data: + copy_of_data.append(data_point.copy()) + return copy_of_data + + + def printData(self): + ''' + #####Description: + Print stored data (to stdout), so you can see what will be sent if "sendData" is called. This is useful for debugging purposes. + + #####Parameters: + None + + #####Return: + None + ''' + for elem in self._data: + print str(elem) + print 'Count: %d' % len(self._data) + + + def removeDataPoint(self, data_point): + ''' + #####Description: + This method delete one data point from the internal stored data. + + It's main purpose is to narrow the internal data to keep only those failed data points (those that were not received/processed by the server) so you can identify/retry them. Data points can be obtained from *sendDataOneByOne* return, or from *getData* return. + + #####Parameters: + * **data_point**: [in] [dict] [mandatory] This is a dictionary as returned by *sendDataOneByOne()* or *getData* methods. + + #####Return: + It returns True if data_point was found and deleted, and False if not. + ''' + if data_point in self._data: + self._data.remove(data_point) + return True + + return False diff --git a/setup.py b/setup.py index 464ebea..ea809d7 100755 --- a/setup.py +++ b/setup.py @@ -8,7 +8,10 @@ def read(fname): setupconf = dict( name="pyZabbixSender", - py_modules=["pyZabbixSender"], + py_modules=[ + "pyZabbixSender", + "pyZabbixSenderBase", + ], author="Kurt Momberg", author_email="kurtqm@yahoo.com.ar", description="Python implementation of zabbix_sender.", From 9bb7c9e12667e1ffcf3f7c7a0e532bbb8210122b Mon Sep 17 00:00:00 2001 From: Vsevolod Novikov Date: Thu, 10 Mar 2016 20:08:58 +0300 Subject: [PATCH 04/17] Async sender has been added --- setup.py | 1 + txZabbixSender.py | 439 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 440 insertions(+) create mode 100644 txZabbixSender.py diff --git a/setup.py b/setup.py index ea809d7..a25e8d3 100755 --- a/setup.py +++ b/setup.py @@ -9,6 +9,7 @@ def read(fname): setupconf = dict( name="pyZabbixSender", py_modules=[ + "txZabbixSender", "pyZabbixSender", "pyZabbixSenderBase", ], diff --git a/txZabbixSender.py b/txZabbixSender.py new file mode 100644 index 0000000..1bcebbf --- /dev/null +++ b/txZabbixSender.py @@ -0,0 +1,439 @@ +# -*- coding: utf-8 +# Copyleft 2016 Vsevolod Novikov +# > Based on work by Kurt Momberg +# >> Based on work by Klimenko Artyem +# >>> Based on work by Rob Cherry +# >>>> Based on work by Enrico Tröger +# License: GNU GPLv2 + +from twisted.internet.endpoints import TCP4ServerEndpoint +from twisted.internet import reactor,address,defer + +from twisted.python import log, failure + +from twisted.internet import protocol, reactor +from zope.interface import implements +from twisted.internet import interfaces,error + +import struct +import time +import sys +import re + +# If you're using an old version of python that don't have json available, +# you can use simplejson instead: https://simplejson.readthedocs.org/en/latest/ +#import simplejson as json +import json + +from pyZabbixSenderBase import pyZabbixSenderBase + +class SenderProtocol(protocol.Protocol): + def __init__(self,factory): + self.factory = factory + self.reset() + + def reset(self): + self.tail = '' + self.state = 'magic' + + def dataReceived(self, data): + log.msg("RECEIVED DATA: %s" % len(data)) + while len(data): + l = self.parseData(data) + log.msg("PARSED DATA: %s" % l) + if l: + data = data[l:] + else: + self.error_happens(failure.Failure(Exception("Unknown data chunk"))) + self.transport.loseConnection() + break + + def parseData(self,data): + d = self.tail + data + l = self._expected_length() + if len(d) < l: + self.tail = d + return len(data) + + self._expected_parse(d[0:l]) + taill = len(self.tail) + self.tail = '' + return l - taill + + def _expected_length(self): + m = getattr(self,'_expected_length_'+self.state) + return m() + + def _expected_parse(self,data): + m = getattr(self,'_expected_parse_'+self.state) + return m(data) + + def _expected_length_magic(self): + return 5 + + def _expected_parse_magic(self,data): + if not data == 'ZBXD\1': + self.error_happens(failure.Failure(Exception("Wrong magic: %s" % data))) + self.transport.loseConnection() + return + self.state = 'header' + + def _expected_length_header(self): + return 8 + + def _expected_parse_header(self,data): + self._data_length, = struct.unpack('i',data[:4]) + log.msg("Received length: %s" % self._data_length) + self.state = 'data' + + def _expected_length_data(self): + return self._data_length + + def _expected_parse_data(self,data): + packet = {} + self.state = 'header' + try: + packet = json.loads(data) + except Exception,ex: + f = failure.Failure() + self.error_happens(f) + self.transport.loseConnection() + return + log.msg("Received packet: %s" % packet) + try: + self.packet_received(packet) + except Exception,ex: + f = failure.Failure() + self.error_happens(f) + self.transport.loseConnection() + return + self.state = 'done' + self.transport.loseConnection() # Normally the Zabbix expects closing connection from the sender + + def packet_received(self,packet): + raise NotImplemented() + + def error_happens(self,fail): + log.err(fail) + + def send_packet(self,packet): + '''sends a packet in form of json''' + log.msg("Sending a packet: %s" % packet) + try: + data = json.dumps(packet) + except Exception,ex: + f = failure.Failure() + self.error_happens(f) + self.transport.loseConnection() + return + data_length = len(data) + data_header = str(struct.pack('q', data_length)) + data_to_send = 'ZBXD\1' + str(data_header) + data + self.transport.write(data_to_send) + log.msg("Packet sent: %s" % data_to_send) + +class SenderProcessor(SenderProtocol): + FAILED_COUNTER = re.compile('^.*failed.+?(\d+).*$') + PROCESSED_COUNTER = re.compile('^.*processed.+?(\d+).*$') + SECONDS_SPENT = re.compile('^.*seconds spent.+?((-|\+|\d|\.|e|E)+).*$') + def __init__(self,factory,packet,deferred): + SenderProtocol.__init__(self,factory) + self.deferred = deferred + self.packet = packet + + def connectionMade(self): + self.send_packet(self.packet) + def packet_received(self,packet): + failed = self.FAILED_COUNTER.match(packet['info'].lower() if 'info' in packet else '') + processed = self.PROCESSED_COUNTER.match(packet['info'].lower() if 'info' in packet else '') + seconds_spent = self.SECONDS_SPENT.match(packet['info'].lower() if 'info' in packet else '') + if failed is None or processed is None: + raise Exception('Unable to parse server response',packet) + failed = int(failed.group(1)) + processed = int(processed.group(1)) + seconds_spent = float(seconds_spent.group(1)) if seconds_spent else None + packet['parsed'] = { + 'failed':failed, + 'processed':processed, + 'seconds spent':seconds_spent + } + #if failed > 0 and processed == 0: + # raise Exception('All failures from zabbix server returned',packet) + self.deferred.callback(packet) + def error_happens(self,fail): + self.deferred.errback(fail) + +class SenderFactory(protocol.ClientFactory): + def __init__(self,packet,deferred): + self.deferred = deferred + self.packet = packet + def buildProtocol(self,addr): + return SenderProcessor(self,self.packet,self.deferred) + + def clientConnectionFailed(self, connector, reason): + if not self.deferred.called: + log.err("ERROR: connecting has been failed because of:%s, sending data has been skipped" % reason) + self.deferred.errback(reason) + + def clientConnectionLost(self, connector, reason): + if not isinstance(reason.value,error.ConnectionDone): + if not self.deferred.called: + log.err("ERROR: connecting has been lost because of:%s, sending data has been skipped" % reason) + self.deferred.errback(reason) + +class txZabbixSender(pyZabbixSenderBase): + ''' + This class allows you to send data to a Zabbix server asynchronously, using the same + protocol used by the zabbix_server binary distributed by Zabbix. + ''' + + def _send(self,packet): + '''This method creates a connection, sends data and returns deferred to get a result''' + deferred = defer.Deferred() + connection = reactor.connectTCP(self.zserver,self.zport,SenderFactory(packet,deferred),self.timeout) + return deferred + + ''' + match = re.match('^.*failed.+?(\d+).*$', response['info'].lower() if 'info' in response else '') + if match is None: + err_message = u'Unable to parse server response - \n%s\n' % str(response) + sys.stderr.write(err_message) + return self.RC_ERR_PARS_RESP, response + else: + fails = int(match.group(1)) + if fails > 0: + if self.verbose is True: + err_message = u'Failures reported by zabbix when sending:\n%s\n' % str(mydata) + sys.stderr.write(err_message) + return self.RC_ERR_FAIL_SEND, response + return self.RC_OK, response + ''' + + def sendData(self, packet_clock=None, max_data_per_conn=None): + ''' + #####Description: + Sends data stored using *addData* method, to the Zabbix server. + + #####Parameters: + * **packet_clock**: [in] [integer] [optional] Zabbix server uses the "clock" parameter in the packet to associate that timestamp to all data values not containing their own clock timestamp. Then: + * If packet_clock is specified, zabbix server will associate it to all data values not containing their own clock. + * If packet_clock is **NOT** specified, zabbix server will use the time when it received the packet as packet clock. + + You can create a timestamp compatible with "clock" or "packet_clock" parameters using this code: + + int(round(time.time())) + *Default value: None* + + * **max_data_per_conn**: [in] [integer] [optional] Allows the user to limit the number of data points sent in one single connection, as some times a too big number can produce problems over slow connections. + + Several "sends" will be automatically performed until all data is sent. + + If omitted, all data points will be sent in one single connection. *Default value: None* + + Please note that **internal data is not deleted after *sendData* is executed**. You need to call *clearData* after sending it, if you want to remove currently stored data. + + #####Return: + A deferred list of each "send" operation results. + ''' + if not max_data_per_conn or max_data_per_conn > len(self._data): + max_data_per_conn = len(self._data) + + responses = [] + i = 0 + while i*max_data_per_conn < len(self._data): + + sender_data = { + "request": "sender data", + "data": [], + } + if packet_clock: + sender_data['clock'] = packet_clock + + sender_data['data'] = self._data[i*max_data_per_conn:(i+1)*max_data_per_conn] + response = self._send(sender_data) + responses.append(response) + i += 1 + + return defer.DeferredList(responses) + + def sendDataOneByOne(self): + ''' + #####Description: + You can use this method to send all stored data, one by one, to determine which traps are not being handled correctly by the server. + + Using this method you'll be able to detect things like: + * hosts not defined in the server + * traps not defined in some particular host + + This is primarily intended for debugging purposes. + + #####Parameters: + None + + #####Return: + A deferred list of each "send" operation results. + ''' + retarray = [] + for i in self._data: + if 'clock' in i: + d = self.sendSingle(i['host'], i['key'], i['value'], i['clock']) + else: + d = self.sendSingle(i['host'], i['key'], i['value']) + + retarray.append(d) + return defer.DeferredList(retarray) + + + def sendSingle(self, host, key, value, clock=None): + ''' + #####Description: + Instead of storing data for sending later, you can use this method to send specific values right now. + + #####Parameters: + It shares the same parameters as the *addData* method. + * **host**: [in] [string] [mandatory] The host which the data is associated to. + * **key**: [in] [string] [mandatory] The name of the trap associated to the host in the Zabbix server. + * **value**: [in] [any] [mandatory] The value you want to send. Please note that you need to take care about the type, as it needs to match key definition in the Zabbix server. Numeric types can be specified as number (for example: 12) or text (for example: "12"). + * **clock**: [in] [integer] [optional] Here you can specify the Unix timestamp associated to your measurement. For example, you can process a log or a data file produced an hour ago, and you want to send the data with the timestamp when the data was produced, not when it was processed by you. If you don't specify this parameter, zabbix server will assign a timestamp when it receives the data. + + You can create a timestamp compatible with "clock" parameter using this code: + int(round(time.time())) + + *Default value: None* + + #####Return: + A deferred for the operation results. + ''' + sender_data = { + "request": "sender data", + "data": [], + } + + obj = self._createDataPoint(host, key, value, clock) + sender_data['data'].append(obj) + return self._send(sender_data) + + + def sendSingleLikeProxy(self, host, key, value, clock=None, proxy=None): + ''' + #####Description: + Use this method to put the data for host monitored by proxy server. This method emulates proxy protocol and data will be accepted by Zabbix server + even if they were send not actually from proxy. + + #####Parameters: + * **host**: [in] [string] [mandatory] The host which the data is associated to. + * **key**: [in] [string] [mandatory] The name of the trap associated to the host in the Zabbix server. + * **value**: [in] [any] [mandatory] The value you want to send. Please note that you need to take care about the type, as it needs to match key definition in the Zabbix server. Numeric types can be specified as number (for example: 12) or text (for example: "12"). + * **clock**: [in] [integer] [optional] Here you can specify the Unix timestamp associated to your measurement. For example, you can process a log or a data file produced an hour ago, and you want to send the data with the timestamp when the data was produced, not when it was processed by you. If you don't specify this parameter, zabbix server will assign a timestamp when it receives the data. + + You can create a timestamp compatible with "clock" parameter using this code: + int(round(time.time())) + + *Default value: None* + + * **proxy**: [in] [string] [optional] The name of the proxy to be recognized by the Zabbix server. If proxy is not specified, a normal "sendSingle" operation will be performed. *Default value: None* + #####Return: + A deferred for the operation results. + ''' + # Proxy was not specified, so we'll do a "normal" sendSingle operation + if proxy is None: + return sendSingle(host, key, value, clock) + + sender_data = { + "request": "history data", + "host": proxy, + "data": [], + } + + obj = self._createDataPoint(host, key, value, clock) + sender_data['data'].append(obj) + return self._send(sender_data) + +##################################### +# --- Examples of usage --- +##################################### +# +# Initiating a pyZabbixSender object - +# z = pyZabbixSender() # Defaults to using ZABBIX_SERVER,ZABBIX_PORT +# z = pyZabbixSender(verbose=True) # Prints all sending failures to stderr +# z = pyZabbixSender(server="172.0.0.100",verbose=True) +# z = pyZabbixSender(server="zabbix-server",port=10051) +# z = pyZabbixSender("zabbix-server", 10051) + +# --- Adding data to send later --- +# Host, Key, Value are all necessary +# z.addData("test_host","test_trap","12") +# +# Optionally you can provide a specific timestamp for the sample +# z.addData("test_host","test_trap","13",1365787627) +# +# If you provide no timestamp, you still can assign one when sending, or let +# zabbix server to put the timestamp when the message is received. + +# --- Printing values --- +# Not that useful, but if you would like to see your data in tuple form: +# z.printData() + +# --- Sending data --- +# +# Just sending a single data point (you don't need to call add_value for this +# to work): +# z.sendSingle("test_host","test_trap","12") +# +# Sending everything at once, with no concern about +# individual item failure - +# +# result = z.sendData() +# for r in result: +# print "Result: %s -> %s" % (str(r[0]), r[1]) +# +# If you're ok with the result, you can delete the data inside the sender, to +# allow a new round of data feed/send. +# z.clearData() +# +# If you want to specify a timestamp to all values without one, you can specify +# the packet_clock parameter: +# z.sendData(packet_clock=1365787627) +# +# When you're sending data over a slow connection, you may find useful the +# possibility to send data in packets with no more than max_data_per_conn +# data points on it. +# All the data will be sent, but in smaller packets. +# For example, if you want to send 4000 data points in packets containing no +# more than 200 of them: +# +# results = z.sendData(max_data_per_conn=200) +# for partial_result in results: +# print partial_result +# +# Sending every item individually so that we can capture +# success or failure +# +# results = z.sendDataOneByOne() +# for (code,data) in results: +# if code != z.RC_OK: +# print "Failed to send: %s" % str(data) +# +# +##################################### +# Mini example of a working program # +##################################### +# +# import sys +# sys.path.append("/path/to/pyZabbixSender.py") +# from pyZabbixSender import pyZabbixSender +# +# z = pyZabbixSender() # Defaults to using ZABBIX_SERVER, ZABBIX_PORT +# z.addData("test_host","test_trap_1","12") +# z.addData("test_host","test_trap_2","13",1366033479) +# +# Two ways of printing internal data +# z.printData() +# print z +# +# results = z.sendDataOneByOne() +# for (code,data) in results: +# if code != z.RC_OK: +# print "Failed to send: %s" % str(data) +# z.clearData() +##################################### From ccd659dbebc4ec229ef05eeb0dc23cbead2f23c4 Mon Sep 17 00:00:00 2001 From: Vsevolod Novikov Date: Thu, 10 Mar 2016 20:09:34 +0300 Subject: [PATCH 05/17] Temporary added local test cases --- test_seva.py | 146 ++++++++++++++++++++++++++++++++++++++++++++++++ test_seva_tx.py | 107 +++++++++++++++++++++++++++++++++++ 2 files changed, 253 insertions(+) create mode 100644 test_seva.py create mode 100644 test_seva_tx.py diff --git a/test_seva.py b/test_seva.py new file mode 100644 index 0000000..9b02625 --- /dev/null +++ b/test_seva.py @@ -0,0 +1,146 @@ +from pyZabbixSender import pyZabbixSender + +# this import is optional. Here is used to create a timestamp to associate +# to some data points, for example/testing purposes only. +import time + +# Specifying server, but using default port +z = pyZabbixSender("95.79.44.111") + +def printBanner(text): + border_char = '#' + border = border_char * (len(text) + 4) + print "\n\n%s" % border + print "%s %s %s" % (border_char, text, border_char) + print border + + +def test_01(): + ''' + Simple "debugging" usage example (using "sendDataOneByOne" method) + ''' + printBanner("test_01") + + # Just ensuring we start without data, in case other example was + # executed before this one + z.clearData() + + # Adding a host/trap that exist + z.addData("local.seva.test", "test", 21) + + # Adding a host that exist, but a trap that doesn't + z.addData("local.seva.test", "test1", 100) + + # Sending stored data, one by one, to know which host/traps have problems + results = z.sendDataOneByOne() + + # You'll get a "results" like: + #[ (0, {'host': 'test_host', 'value': 21, 'key': 'test_trap'}) + # (1, {'host': 'test_host', 'value': 100, 'key': 'test_trap1'}) + #] + print "---- Results content:" + print results + + # What if we want to remove data already sent, and retry or do something + # else with the data no sent? + print "\n---- Data before the cleaning:\n%s\n" % str(z) + for (code, data_point) in results: + if code != z.RC_OK: + print "Failed to send: %s" % str(data_point) + else: + # This data_point was successfully sent, so we can remove from internal data + z.removeDataPoint(data_point) + print "\n---- Data after the cleaning:\n%s\n" % str(z) + + # at this point you can even retry sending the data calling the "sendDataOneByOne" + # or "sendData" methods + + +def test_02(): + ''' + Testing "max_data_per_conn" parameter in "sendData" method + ''' + printBanner("test_02") + + # Just ensuring we start without data, in case other example was + # executed before this one + z.clearData() + + # Adding some valid data + for i in range (10): + z.addData("local.seva.test", "test", i) + + # Now adding a trap that doesn't exist in the server + z.addData("local.seva.test", "test1", 3) + + results = z.sendData(max_data_per_conn=3) + + # Now lets take a look at the return. Should be something like this: + #[ (0, {u'info': u'Processed 3 Failed 0 Total 3 Seconds spent 0.000062', u'response': u'success'}), + # (0, {u'info': u'Processed 3 Failed 0 Total 3 Seconds spent 0.000057', u'response': u'success'}), + # (0, {u'info': u'Processed 3 Failed 0 Total 3 Seconds spent 0.000056', u'response': u'success'}), + # (1, {u'info': u'Processed 1 Failed 1 Total 2 Seconds spent 0.000041', u'response': u'success'})] + print results + + +def test_03(): + ''' + Testing method "sendSingle" + ''' + printBanner("test_03") + + # We don't need to clean internal data, because we'll send data given to the method + + # Sending data right now, without timestamp + result = z.sendSingle("local.seva.test", "test", 1) + + print "---- After sendSingle without timestamp" + print result + + # Now sending data with timestamp + result = z.sendSingle("local.seva.test", "test", 1, int(round(time.time()))) + print "\n---- After sendSingle with timestamp" + print result + + +def test_04(): + ''' + Testing getData method. + ''' + printBanner("test_04") + + # Just ensuring we start without data, in case other example was + # executed before this one + z.clearData() + + # Adding data + z.addData("local.seva.test", "test", 1) + z.addData("local.seva.test", "test", 2) + + # Showing current data + print "---- Showing stored data:" + print z + + # remember that getData returns a copy of the data + copy_of_data = z.getData() + print "\n---- Showing data returned:" + print copy_of_data + + # We'll modify returned data, to show this won't affect internal data + print "\n---- Modifying returned data" + copy_of_data.append({'host': 'local.seva.test', 'value': 500, 'key': 'test'}) + + # Showing current data + print "\n---- Showing stored data again (note is the same as before):" + print z + + print "\n---- Showing returned and modified data:" + print copy_of_data + + + +# Here you can execute the test/examples you want +test_01() +test_02() +test_03() +test_04() \ No newline at end of file diff --git a/test_seva_tx.py b/test_seva_tx.py new file mode 100644 index 0000000..2ae10af --- /dev/null +++ b/test_seva_tx.py @@ -0,0 +1,107 @@ +from txZabbixSender import txZabbixSender + +# this import is optional. Here is used to create a timestamp to associate +# to some data points, for example/testing purposes only. +import time +import sys + +from twisted.internet import reactor, defer + +from twisted.python import log +log.startLogging(sys.stdout,False) + +# Specifying server, but using default port +z = txZabbixSender("95.79.44.111") + +def printBanner(text): + border_char = '#' + border = border_char * (len(text) + 4) + print "\n\n%s" % border + print "%s %s %s" % (border_char, text, border_char) + print border + + +@defer.inlineCallbacks +def tests(): + + + yield test_01() + yield test_02() + yield test_03() + reactor.stop() + +@defer.inlineCallbacks +def test_01(): + ''' + Simple "debugging" usage example (using "sendDataOneByOne" method) + ''' + printBanner("test_01") + + # Just ensuring we start without data, in case other example was + # executed before this one + z.clearData() + + # Adding a host/trap that exist + z.addData("local.seva.test", "test", 21) + + # Adding a host that exist, but a trap that doesn't + z.addData("local.seva.test", "test1", 100) + + # Sending stored data, one by one, to know which host/traps have problems + results = yield z.sendDataOneByOne() + + # You'll get a "results", note that they differ from sync code! + print "---- Results content:" + print results + +@defer.inlineCallbacks +def test_02(): + ''' + Testing "max_data_per_conn" parameter in "sendData" method + ''' + printBanner("test_02") + + # Just ensuring we start without data, in case other example was + # executed before this one + z.clearData() + + # Adding some valid data + for i in range (10): + z.addData("local.seva.test", "test", i) + + # Now adding a trap that doesn't exist in the server + z.addData("local.seva.test", "test1", 3) + + results = yield z.sendData(max_data_per_conn=3) + + # Now lets take a look at the return. + # You'll get a "results", note that they differ from sync code! + print "---- Results content:" + print results + + +@defer.inlineCallbacks +def test_03(): + ''' + Testing method "sendSingle" + ''' + printBanner("test_03") + + # We don't need to clean internal data, because we'll send data given to the method + + # Sending data right now, without timestamp + result = yield z.sendSingle("local.seva.test", "test", 1) + + print "---- After sendSingle without timestamp" + print result + + # Now sending data with timestamp + result = yield z.sendSingle("local.seva.test", "test", 1, int(round(time.time()))) + print "\n---- After sendSingle with timestamp" + print result + + + +# Here you can execute the test/examples you want +reactor.callWhenRunning(tests) +reactor.run() From 4b1d139f604e9a49debc058337338e4c4ab1438e Mon Sep 17 00:00:00 2001 From: Vsevolod Novikov Date: Fri, 11 Mar 2016 16:02:06 +0300 Subject: [PATCH 06/17] Update README.md --- README.md | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a4f2ee0..b1cd3f5 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,29 @@ Python implementation of zabbix_sender. This is a module that allows you to send data to a [Zabbix] server using Python. You don't need the zabbix_sender binary anymore. +Users of the [Twisted] library can use an asynchronous version of the sender. + Has been tested with Python 2.5.1 and 2.7 Python 2.5.1 doesn't have a json module (needed to implement zabbix protocol), so you can use [simplejson] instead. +Installation +------------ + +Install the package using a pip from the original repository: +```bash +pip install "git+git@github.com:kmomberg/pyZabbixSender.git" +``` +or from one of the mirrors, like: +```bash +pip install "git+git@github.com:baseride/pyZabbixSender.git" +``` + +Usage +----- + Source code contains samples and comments to allows you start using it in no time. Here's a small example: + ```python # Creating a sender object z = pyZabbixSender(server="zabbix-server", port=10051) @@ -35,8 +53,7 @@ z.clearData() z.sendSingle("test_host","test_trap","12") ``` -There are some more options, so take a look and discover how easy is to use it ;) - +There are some more options, so take a look at the [wiki] page and discover how easy is to use it ;) License ---- @@ -46,3 +63,4 @@ GNU GPLv2 [Zabbix]:http://www.zabbix.com/ [simplejson]:https://simplejson.readthedocs.org/en/latest/ [wiki]:https://github.com/kmomberg/pyZabbixSender/wiki +[Twisted]:https://twistedmatrix.com From c0b11e5ccb3612d322cb743b30bbd3a6549003b1 Mon Sep 17 00:00:00 2001 From: Vsevolod Novikov Date: Fri, 11 Mar 2016 16:18:21 +0300 Subject: [PATCH 07/17] Update README.md Async example --- README.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/README.md b/README.md index b1cd3f5..4de251b 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,8 @@ Usage Source code contains samples and comments to allows you start using it in no time. Here's a small example: ```python +from pyZabbixSender import pyZabbixSender + # Creating a sender object z = pyZabbixSender(server="zabbix-server", port=10051) @@ -53,6 +55,41 @@ z.clearData() z.sendSingle("test_host","test_trap","12") ``` +The asynchronous code looks mostly the same, except asynchronous calls to zend...() functions and result processing: + +```python +from txZabbixSender import txZabbixSender +from twisted.internet import reactor, defer + +@defer.inlineCallbacks +def test(): + # Creating a sender object + z = txZabbixSender(server="zabbix-server", port=10051) + + # Adding data (without timestamp) + z.addData(hostname="test_host", key="test_trap_1", value="12") + z.addData("test_host", "test_trap_2", "2.43") + + # Adding data (with timestamp) + z.addData("test_host", "test_trap_2", "2.43", 1365787627) + + # Ready to send your data? + results = yield z.sendData() # NOTE an asynchronous call + + # Check if everything was sent as expected + if not results[0][0]: # NOTE the asynchronous call returns a slightly dirrerent structure + print "oops! Sending data has been failed" + elif results[0][1]['parsed']['processed'] != 3: + print "oops! Zabbix doesn't recognize passed identities" + + # Clear internal data to start populating again + z.clearData() + + # Wants to send a single data point right now? + yield z.sendSingle("test_host","test_trap","12") # NOTE an asynchronous call +``` + + There are some more options, so take a look at the [wiki] page and discover how easy is to use it ;) License From 880553a4994dbd2e4553d5c4b2d1079bf20b664d Mon Sep 17 00:00:00 2001 From: Vsevolod Novikov Date: Fri, 11 Mar 2016 21:00:16 +0300 Subject: [PATCH 08/17] Unified interface --- pyZabbixSender.py | 7 +- pyZabbixSenderBase.py | 9 +- syZabbixSender.py | 298 ++++++++++++++++++++++++++++++++++++++++++ txZabbixSender.py | 26 +--- 4 files changed, 311 insertions(+), 29 deletions(-) create mode 100644 syZabbixSender.py diff --git a/pyZabbixSender.py b/pyZabbixSender.py index bc0011c..477483c 100644 --- a/pyZabbixSender.py +++ b/pyZabbixSender.py @@ -11,12 +11,7 @@ import sys import re -# If you're using an old version of python that don't have json available, -# you can use simplejson instead: https://simplejson.readthedocs.org/en/latest/ -#import simplejson as json -import json - -from pyZabbixSenderBase import pyZabbixSenderBase +from pyZabbixSenderBase import * class pyZabbixSender(pyZabbixSenderBase): ''' diff --git a/pyZabbixSenderBase.py b/pyZabbixSenderBase.py index 50c0e05..3e5a2fe 100644 --- a/pyZabbixSenderBase.py +++ b/pyZabbixSenderBase.py @@ -12,8 +12,13 @@ # If you're using an old version of python that don't have json available, # you can use simplejson instead: https://simplejson.readthedocs.org/en/latest/ -#import simplejson as json -import json +try: + import json +except ImportError: + import simplejson as json + +class InvalidResponse(IOError): + pass class pyZabbixSenderBase: ''' diff --git a/syZabbixSender.py b/syZabbixSender.py new file mode 100644 index 0000000..34a208f --- /dev/null +++ b/syZabbixSender.py @@ -0,0 +1,298 @@ +# -*- coding: utf-8 +# Copyleft 2016 Vsevolod Novikov +# > Based on work by Kurt Momberg +# >> Based on work by Klimenko Artyem +# >>> Based on work by Rob Cherry +# >>>> Based on work by Enrico Tröger +# License: GNU GPLv2 + +import socket +import struct +import time +import sys +import re + +from pyZabbixSenderBase import * + +class syZabbixSender(pyZabbixSenderBase): + ''' + This class allows you to send data to a Zabbix server, using the same + protocol used by the zabbix_server binary distributed by Zabbix. + + It uses exceptions to report errors. + ''' + + FAILED_COUNTER = re.compile('^.*failed.+?(\d+).*$') + PROCESSED_COUNTER = re.compile('^.*processed.+?(\d+).*$') + SECONDS_SPENT = re.compile('^.*seconds spent.+?((-|\+|\d|\.|e|E)+).*$') + + def send_packet(self, packet): + ''' + This is the method that actually sends the data to the zabbix server. + ''' + mydata = json.dumps(packet) + socket.setdefaulttimeout(self.timeout) + data_length = len(mydata) + data_header = str(struct.pack('q', data_length)) + data_to_send = 'ZBXD\1' + str(data_header) + str(mydata) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect((self.zserver, self.zport)) + sock.send(data_to_send) + + response_header = sock.recv(5) + if not response_header == 'ZBXD\1': + raise InvalidResponse('Wrong magic: %s' % response_header) + + response_data_header = sock.recv(8) + response_data_header = response_data_header[:4] + response_len = struct.unpack('i', response_data_header)[0] + response_raw = sock.recv(response_len) + sock.close() + response = json.loads(response_raw) + failed = self.FAILED_COUNTER.match(packet['info'].lower() if 'info' in packet else '') + processed = self.PROCESSED_COUNTER.match(packet['info'].lower() if 'info' in packet else '') + seconds_spent = self.SECONDS_SPENT.match(packet['info'].lower() if 'info' in packet else '') + if failed is None or processed is None: + raise InvalidResponse('Unable to parse server response',packet) + failed = int(failed.group(1)) + processed = int(processed.group(1)) + seconds_spent = float(seconds_spent.group(1)) if seconds_spent else None + response['info'] = { + 'failed':failed, + 'processed':processed, + 'seconds spent':seconds_spent + } + return response + + def sendData(self, packet_clock=None, max_data_per_conn=None): + ''' + #####Description: + Sends data stored using *addData* method, to the Zabbix server. + + #####Parameters: + * **packet_clock**: [in] [integer] [optional] Zabbix server uses the "clock" parameter in the packet to associate that timestamp to all data values not containing their own clock timestamp. Then: + * If packet_clock is specified, zabbix server will associate it to all data values not containing their own clock. + * If packet_clock is **NOT** specified, zabbix server will use the time when it received the packet as packet clock. + + You can create a timestamp compatible with "clock" or "packet_clock" parameters using this code: + + int(round(time.time())) + *Default value: None* + + * **max_data_per_conn**: [in] [integer] [optional] Allows the user to limit the number of data points sent in one single connection, as some times a too big number can produce problems over slow connections. + + Several "sends" will be automatically performed until all data is sent. + + If omitted, all data points will be sent in one single connection. *Default value: None* + + Please note that **internal data is not deleted after *sendData* is executed**. You need to call *clearData* after sending it, if you want to remove currently stored data. + + #####Return: + A list of *(result, msg)* associated to each "send" operation, where *result* is a boolean meaning success of the operation, + and *msg* is a message from the server in case of success, or exception in case of error. + + In case of success, the server returns a message which is parsed by the function. The server message + contains counters for *processed* and *failed* (ignored) data items. Note that even if processed + data counter is 0 and all data items have been failed, it does not mean the error condition. + ''' + if not max_data_per_conn or max_data_per_conn > len(self._data): + max_data_per_conn = len(self._data) + + responses = [] + i = 0 + while i*max_data_per_conn < len(self._data): + + sender_data = { + "request": "sender data", + "data": [], + } + if packet_clock: + sender_data['clock'] = packet_clock + + sender_data['data'] = self._data[i*max_data_per_conn:(i+1)*max_data_per_conn] + try: + response = self.send_packet(sender_data) + except Exception,ex: + responses.append((False,ex)) + else: + responses.append((True,response)) + i += 1 + + return responses + + def sendDataOneByOne(self): + ''' + #####Description: + You can use this method to send all stored data, one by one, to determine which traps are not being handled correctly by the server. + + Using this method you'll be able to detect things like: + * hosts not defined in the server + * traps not defined in some particular host + + This is primarily intended for debugging purposes. + + #####Parameters: + None + + #####Return: + A list of *(result, msg)* associated to each "send" operation, where *result* is a boolean meaning success of the operation, + and *msg* is a message from the server in case of success, or exception in case of error. + + In case of success, the server returns a message which is parsed by the function. The server message + contains counters for *processed* and *failed* (ignored) data items. Note that even if processed + data counter is 0 and all data items have been failed, it does not mean the error condition. + ''' + return self.sendData(max_data_per_conn=1) + + def sendSingle(self, host, key, value, clock=None): + ''' + #####Description: + Instead of storing data for sending later, you can use this method to send specific values right now. + + #####Parameters: + It shares the same parameters as the *addData* method. + * **host**: [in] [string] [mandatory] The host which the data is associated to. + * **key**: [in] [string] [mandatory] The name of the trap associated to the host in the Zabbix server. + * **value**: [in] [any] [mandatory] The value you want to send. Please note that you need to take care about the type, as it needs to match key definition in the Zabbix server. Numeric types can be specified as number (for example: 12) or text (for example: "12"). + * **clock**: [in] [integer] [optional] Here you can specify the Unix timestamp associated to your measurement. For example, you can process a log or a data file produced an hour ago, and you want to send the data with the timestamp when the data was produced, not when it was processed by you. If you don't specify this parameter, zabbix server will assign a timestamp when it receives the data. + + You can create a timestamp compatible with "clock" parameter using this code: + int(round(time.time())) + + *Default value: None* + + #####Return: + A message returned by the server. + ''' + sender_data = { + "request": "sender data", + "data": [], + } + + obj = self._createDataPoint(host, key, value, clock) + sender_data['data'].append(obj) + return self.send_packet(sender_data) + + def sendSingleLikeProxy(self, host, key, value, clock=None, proxy=None): + ''' + #####Description: + Use this method to put the data for host monitored by proxy server. This method emulates proxy protocol and data will be accepted by Zabbix server + even if they were send not actually from proxy. + + #####Parameters: + * **host**: [in] [string] [mandatory] The host which the data is associated to. + * **key**: [in] [string] [mandatory] The name of the trap associated to the host in the Zabbix server. + * **value**: [in] [any] [mandatory] The value you want to send. Please note that you need to take care about the type, as it needs to match key definition in the Zabbix server. Numeric types can be specified as number (for example: 12) or text (for example: "12"). + * **clock**: [in] [integer] [optional] Here you can specify the Unix timestamp associated to your measurement. For example, you can process a log or a data file produced an hour ago, and you want to send the data with the timestamp when the data was produced, not when it was processed by you. If you don't specify this parameter, zabbix server will assign a timestamp when it receives the data. + + You can create a timestamp compatible with "clock" parameter using this code: + int(round(time.time())) + + *Default value: None* + + * **proxy**: [in] [string] [optional] The name of the proxy to be recognized by the Zabbix server. If proxy is not specified, a normal "sendSingle" operation will be performed. *Default value: None* + #####Return: + A message returned by the server. + ''' + # Proxy was not specified, so we'll do a "normal" sendSingle operation + if proxy is None: + return sendSingle(host, key, value, clock) + + sender_data = { + "request": "history data", + "host": proxy, + "data": [], + } + + obj = self._createDataPoint(host, key, value, clock) + sender_data['data'].append(obj) + return self.send_packet(sender_data) + +##################################### +# --- Examples of usage --- +##################################### +# +# Initiating a pyZabbixSender object - +# z = pyZabbixSender() # Defaults to using ZABBIX_SERVER,ZABBIX_PORT +# z = pyZabbixSender(verbose=True) # Prints all sending failures to stderr +# z = pyZabbixSender(server="172.0.0.100",verbose=True) +# z = pyZabbixSender(server="zabbix-server",port=10051) +# z = pyZabbixSender("zabbix-server", 10051) + +# --- Adding data to send later --- +# Host, Key, Value are all necessary +# z.addData("test_host","test_trap","12") +# +# Optionally you can provide a specific timestamp for the sample +# z.addData("test_host","test_trap","13",1365787627) +# +# If you provide no timestamp, you still can assign one when sending, or let +# zabbix server to put the timestamp when the message is received. + +# --- Printing values --- +# Not that useful, but if you would like to see your data in tuple form: +# z.printData() + +# --- Sending data --- +# +# Just sending a single data point (you don't need to call add_value for this +# to work): +# z.sendSingle("test_host","test_trap","12") +# +# Sending everything at once, with no concern about +# individual item failure - +# +# result = z.sendData() +# for r in result: +# print "Result: %s -> %s" % (str(r[0]), r[1]) +# +# If you're ok with the result, you can delete the data inside the sender, to +# allow a new round of data feed/send. +# z.clearData() +# +# If you want to specify a timestamp to all values without one, you can specify +# the packet_clock parameter: +# z.sendData(packet_clock=1365787627) +# +# When you're sending data over a slow connection, you may find useful the +# possibility to send data in packets with no more than max_data_per_conn +# data points on it. +# All the data will be sent, but in smaller packets. +# For example, if you want to send 4000 data points in packets containing no +# more than 200 of them: +# +# results = z.sendData(max_data_per_conn=200) +# for partial_result in results: +# print partial_result +# +# Sending every item individually so that we can capture +# success or failure +# +# results = z.sendDataOneByOne() +# for (code,data) in results: +# if code != z.RC_OK: +# print "Failed to send: %s" % str(data) +# +# +##################################### +# Mini example of a working program # +##################################### +# +# import sys +# sys.path.append("/path/to/pyZabbixSender.py") +# from pyZabbixSender import pyZabbixSender +# +# z = pyZabbixSender() # Defaults to using ZABBIX_SERVER, ZABBIX_PORT +# z.addData("test_host","test_trap_1","12") +# z.addData("test_host","test_trap_2","13",1366033479) +# +# Two ways of printing internal data +# z.printData() +# print z +# +# results = z.sendDataOneByOne() +# for (code,data) in results: +# if code != z.RC_OK: +# print "Failed to send: %s" % str(data) +# z.clearData() +##################################### diff --git a/txZabbixSender.py b/txZabbixSender.py index 1bcebbf..36f9341 100644 --- a/txZabbixSender.py +++ b/txZabbixSender.py @@ -25,7 +25,7 @@ #import simplejson as json import json -from pyZabbixSenderBase import pyZabbixSenderBase +from pyZabbixSenderBase import * class SenderProtocol(protocol.Protocol): def __init__(self,factory): @@ -44,7 +44,7 @@ def dataReceived(self, data): if l: data = data[l:] else: - self.error_happens(failure.Failure(Exception("Unknown data chunk"))) + self.error_happens(failure.Failure(InvalidResponse("Unknown data chunk"))) self.transport.loseConnection() break @@ -73,7 +73,7 @@ def _expected_length_magic(self): def _expected_parse_magic(self,data): if not data == 'ZBXD\1': - self.error_happens(failure.Failure(Exception("Wrong magic: %s" % data))) + self.error_happens(failure.Failure(InvalidResponse("Wrong magic: %s" % data))) self.transport.loseConnection() return self.state = 'header' @@ -148,11 +148,11 @@ def packet_received(self,packet): processed = self.PROCESSED_COUNTER.match(packet['info'].lower() if 'info' in packet else '') seconds_spent = self.SECONDS_SPENT.match(packet['info'].lower() if 'info' in packet else '') if failed is None or processed is None: - raise Exception('Unable to parse server response',packet) + raise InvalidResponse('Unable to parse server response',packet) failed = int(failed.group(1)) processed = int(processed.group(1)) seconds_spent = float(seconds_spent.group(1)) if seconds_spent else None - packet['parsed'] = { + packet['info'] = { 'failed':failed, 'processed':processed, 'seconds spent':seconds_spent @@ -193,22 +193,6 @@ def _send(self,packet): connection = reactor.connectTCP(self.zserver,self.zport,SenderFactory(packet,deferred),self.timeout) return deferred - ''' - match = re.match('^.*failed.+?(\d+).*$', response['info'].lower() if 'info' in response else '') - if match is None: - err_message = u'Unable to parse server response - \n%s\n' % str(response) - sys.stderr.write(err_message) - return self.RC_ERR_PARS_RESP, response - else: - fails = int(match.group(1)) - if fails > 0: - if self.verbose is True: - err_message = u'Failures reported by zabbix when sending:\n%s\n' % str(mydata) - sys.stderr.write(err_message) - return self.RC_ERR_FAIL_SEND, response - return self.RC_OK, response - ''' - def sendData(self, packet_clock=None, max_data_per_conn=None): ''' #####Description: From d739b104ed644ad74c9904c95658ef2efe60b2cc Mon Sep 17 00:00:00 2001 From: Vsevolod Novikov Date: Tue, 15 Mar 2016 16:43:56 +0300 Subject: [PATCH 09/17] Unified interface --- pyZabbixSenderBase.py | 2 +- syZabbixSender.py | 8 ++++---- test_seva.py | 26 +++----------------------- 3 files changed, 8 insertions(+), 28 deletions(-) diff --git a/pyZabbixSenderBase.py b/pyZabbixSenderBase.py index 3e5a2fe..d793946 100644 --- a/pyZabbixSenderBase.py +++ b/pyZabbixSenderBase.py @@ -17,7 +17,7 @@ except ImportError: import simplejson as json -class InvalidResponse(IOError): +class InvalidResponse(Exception): pass class pyZabbixSenderBase: diff --git a/syZabbixSender.py b/syZabbixSender.py index 34a208f..3434d21 100644 --- a/syZabbixSender.py +++ b/syZabbixSender.py @@ -49,11 +49,11 @@ def send_packet(self, packet): response_raw = sock.recv(response_len) sock.close() response = json.loads(response_raw) - failed = self.FAILED_COUNTER.match(packet['info'].lower() if 'info' in packet else '') - processed = self.PROCESSED_COUNTER.match(packet['info'].lower() if 'info' in packet else '') - seconds_spent = self.SECONDS_SPENT.match(packet['info'].lower() if 'info' in packet else '') + failed = self.FAILED_COUNTER.match(response['info'].lower() if 'info' in response else '') + processed = self.PROCESSED_COUNTER.match(response['info'].lower() if 'info' in response else '') + seconds_spent = self.SECONDS_SPENT.match(response['info'].lower() if 'info' in response else '') if failed is None or processed is None: - raise InvalidResponse('Unable to parse server response',packet) + raise InvalidResponse('Unable to parse server response',packet,response) failed = int(failed.group(1)) processed = int(processed.group(1)) seconds_spent = float(seconds_spent.group(1)) if seconds_spent else None diff --git a/test_seva.py b/test_seva.py index 9b02625..2e956e2 100644 --- a/test_seva.py +++ b/test_seva.py @@ -1,11 +1,11 @@ -from pyZabbixSender import pyZabbixSender +from syZabbixSender import syZabbixSender # this import is optional. Here is used to create a timestamp to associate # to some data points, for example/testing purposes only. import time # Specifying server, but using default port -z = pyZabbixSender("95.79.44.111") +z = syZabbixSender("95.79.44.111") def printBanner(text): border_char = '#' @@ -40,21 +40,6 @@ def test_01(): #] print "---- Results content:" print results - - # What if we want to remove data already sent, and retry or do something - # else with the data no sent? - print "\n---- Data before the cleaning:\n%s\n" % str(z) - for (code, data_point) in results: - if code != z.RC_OK: - print "Failed to send: %s" % str(data_point) - else: - # This data_point was successfully sent, so we can remove from internal data - z.removeDataPoint(data_point) - print "\n---- Data after the cleaning:\n%s\n" % str(z) - - # at this point you can even retry sending the data calling the "sendDataOneByOne" - # or "sendData" methods - def test_02(): ''' @@ -74,12 +59,7 @@ def test_02(): z.addData("local.seva.test", "test1", 3) results = z.sendData(max_data_per_conn=3) - - # Now lets take a look at the return. Should be something like this: - #[ (0, {u'info': u'Processed 3 Failed 0 Total 3 Seconds spent 0.000062', u'response': u'success'}), - # (0, {u'info': u'Processed 3 Failed 0 Total 3 Seconds spent 0.000057', u'response': u'success'}), - # (0, {u'info': u'Processed 3 Failed 0 Total 3 Seconds spent 0.000056', u'response': u'success'}), - # (1, {u'info': u'Processed 1 Failed 1 Total 2 Seconds spent 0.000041', u'response': u'success'})] + print "---- Results content:" print results From 222124b1d964fba913509fcb424f9323e92682ee Mon Sep 17 00:00:00 2001 From: Vsevolod Novikov Date: Tue, 15 Mar 2016 16:56:16 +0300 Subject: [PATCH 10/17] Refactoried a bit --- pyZabbixSenderBase.py | 24 +++++++++ syZabbixSender.py | 109 +--------------------------------------- txZabbixSender.py | 112 ++---------------------------------------- 3 files changed, 28 insertions(+), 217 deletions(-) diff --git a/pyZabbixSenderBase.py b/pyZabbixSenderBase.py index d793946..c0f61a0 100644 --- a/pyZabbixSenderBase.py +++ b/pyZabbixSenderBase.py @@ -159,3 +159,27 @@ def removeDataPoint(self, data_point): return True return False + +def recognize_response_raw(response_raw): + return recognize_response(json.loads(response_raw)) + +FAILED_COUNTER = re.compile('^.*failed.+?(\d+).*$') +PROCESSED_COUNTER = re.compile('^.*processed.+?(\d+).*$') +SECONDS_SPENT = re.compile('^.*seconds spent.+?((-|\+|\d|\.|e|E)+).*$') + +def recognize_response(response): + failed = FAILED_COUNTER.match(response['info'].lower() if 'info' in response else '') + processed = PROCESSED_COUNTER.match(response['info'].lower() if 'info' in response else '') + seconds_spent = SECONDS_SPENT.match(response['info'].lower() if 'info' in response else '') + + if failed is None or processed is None: + raise InvalidResponse('Unable to parse server response',packet,response_raw) + failed = int(failed.group(1)) + processed = int(processed.group(1)) + seconds_spent = float(seconds_spent.group(1)) if seconds_spent else None + response['info'] = { + 'failed':failed, + 'processed':processed, + 'seconds spent':seconds_spent + } + return response diff --git a/syZabbixSender.py b/syZabbixSender.py index 3434d21..6af0f26 100644 --- a/syZabbixSender.py +++ b/syZabbixSender.py @@ -22,10 +22,6 @@ class syZabbixSender(pyZabbixSenderBase): It uses exceptions to report errors. ''' - FAILED_COUNTER = re.compile('^.*failed.+?(\d+).*$') - PROCESSED_COUNTER = re.compile('^.*processed.+?(\d+).*$') - SECONDS_SPENT = re.compile('^.*seconds spent.+?((-|\+|\d|\.|e|E)+).*$') - def send_packet(self, packet): ''' This is the method that actually sends the data to the zabbix server. @@ -48,21 +44,7 @@ def send_packet(self, packet): response_len = struct.unpack('i', response_data_header)[0] response_raw = sock.recv(response_len) sock.close() - response = json.loads(response_raw) - failed = self.FAILED_COUNTER.match(response['info'].lower() if 'info' in response else '') - processed = self.PROCESSED_COUNTER.match(response['info'].lower() if 'info' in response else '') - seconds_spent = self.SECONDS_SPENT.match(response['info'].lower() if 'info' in response else '') - if failed is None or processed is None: - raise InvalidResponse('Unable to parse server response',packet,response) - failed = int(failed.group(1)) - processed = int(processed.group(1)) - seconds_spent = float(seconds_spent.group(1)) if seconds_spent else None - response['info'] = { - 'failed':failed, - 'processed':processed, - 'seconds spent':seconds_spent - } - return response + return recognize_response_raw(response_raw) def sendData(self, packet_clock=None, max_data_per_conn=None): ''' @@ -207,92 +189,3 @@ def sendSingleLikeProxy(self, host, key, value, clock=None, proxy=None): obj = self._createDataPoint(host, key, value, clock) sender_data['data'].append(obj) return self.send_packet(sender_data) - -##################################### -# --- Examples of usage --- -##################################### -# -# Initiating a pyZabbixSender object - -# z = pyZabbixSender() # Defaults to using ZABBIX_SERVER,ZABBIX_PORT -# z = pyZabbixSender(verbose=True) # Prints all sending failures to stderr -# z = pyZabbixSender(server="172.0.0.100",verbose=True) -# z = pyZabbixSender(server="zabbix-server",port=10051) -# z = pyZabbixSender("zabbix-server", 10051) - -# --- Adding data to send later --- -# Host, Key, Value are all necessary -# z.addData("test_host","test_trap","12") -# -# Optionally you can provide a specific timestamp for the sample -# z.addData("test_host","test_trap","13",1365787627) -# -# If you provide no timestamp, you still can assign one when sending, or let -# zabbix server to put the timestamp when the message is received. - -# --- Printing values --- -# Not that useful, but if you would like to see your data in tuple form: -# z.printData() - -# --- Sending data --- -# -# Just sending a single data point (you don't need to call add_value for this -# to work): -# z.sendSingle("test_host","test_trap","12") -# -# Sending everything at once, with no concern about -# individual item failure - -# -# result = z.sendData() -# for r in result: -# print "Result: %s -> %s" % (str(r[0]), r[1]) -# -# If you're ok with the result, you can delete the data inside the sender, to -# allow a new round of data feed/send. -# z.clearData() -# -# If you want to specify a timestamp to all values without one, you can specify -# the packet_clock parameter: -# z.sendData(packet_clock=1365787627) -# -# When you're sending data over a slow connection, you may find useful the -# possibility to send data in packets with no more than max_data_per_conn -# data points on it. -# All the data will be sent, but in smaller packets. -# For example, if you want to send 4000 data points in packets containing no -# more than 200 of them: -# -# results = z.sendData(max_data_per_conn=200) -# for partial_result in results: -# print partial_result -# -# Sending every item individually so that we can capture -# success or failure -# -# results = z.sendDataOneByOne() -# for (code,data) in results: -# if code != z.RC_OK: -# print "Failed to send: %s" % str(data) -# -# -##################################### -# Mini example of a working program # -##################################### -# -# import sys -# sys.path.append("/path/to/pyZabbixSender.py") -# from pyZabbixSender import pyZabbixSender -# -# z = pyZabbixSender() # Defaults to using ZABBIX_SERVER, ZABBIX_PORT -# z.addData("test_host","test_trap_1","12") -# z.addData("test_host","test_trap_2","13",1366033479) -# -# Two ways of printing internal data -# z.printData() -# print z -# -# results = z.sendDataOneByOne() -# for (code,data) in results: -# if code != z.RC_OK: -# print "Failed to send: %s" % str(data) -# z.clearData() -##################################### diff --git a/txZabbixSender.py b/txZabbixSender.py index 36f9341..2e1e2b0 100644 --- a/txZabbixSender.py +++ b/txZabbixSender.py @@ -130,12 +130,9 @@ def send_packet(self,packet): data_header = str(struct.pack('q', data_length)) data_to_send = 'ZBXD\1' + str(data_header) + data self.transport.write(data_to_send) - log.msg("Packet sent: %s" % data_to_send) + log.msg("Packet sent: %s bytes" % len(data_to_send)) class SenderProcessor(SenderProtocol): - FAILED_COUNTER = re.compile('^.*failed.+?(\d+).*$') - PROCESSED_COUNTER = re.compile('^.*processed.+?(\d+).*$') - SECONDS_SPENT = re.compile('^.*seconds spent.+?((-|\+|\d|\.|e|E)+).*$') def __init__(self,factory,packet,deferred): SenderProtocol.__init__(self,factory) self.deferred = deferred @@ -144,22 +141,8 @@ def __init__(self,factory,packet,deferred): def connectionMade(self): self.send_packet(self.packet) def packet_received(self,packet): - failed = self.FAILED_COUNTER.match(packet['info'].lower() if 'info' in packet else '') - processed = self.PROCESSED_COUNTER.match(packet['info'].lower() if 'info' in packet else '') - seconds_spent = self.SECONDS_SPENT.match(packet['info'].lower() if 'info' in packet else '') - if failed is None or processed is None: - raise InvalidResponse('Unable to parse server response',packet) - failed = int(failed.group(1)) - processed = int(processed.group(1)) - seconds_spent = float(seconds_spent.group(1)) if seconds_spent else None - packet['info'] = { - 'failed':failed, - 'processed':processed, - 'seconds spent':seconds_spent - } - #if failed > 0 and processed == 0: - # raise Exception('All failures from zabbix server returned',packet) - self.deferred.callback(packet) + response = recognize_response(packet) + self.deferred.callback(response) def error_happens(self,fail): self.deferred.errback(fail) @@ -332,92 +315,3 @@ def sendSingleLikeProxy(self, host, key, value, clock=None, proxy=None): obj = self._createDataPoint(host, key, value, clock) sender_data['data'].append(obj) return self._send(sender_data) - -##################################### -# --- Examples of usage --- -##################################### -# -# Initiating a pyZabbixSender object - -# z = pyZabbixSender() # Defaults to using ZABBIX_SERVER,ZABBIX_PORT -# z = pyZabbixSender(verbose=True) # Prints all sending failures to stderr -# z = pyZabbixSender(server="172.0.0.100",verbose=True) -# z = pyZabbixSender(server="zabbix-server",port=10051) -# z = pyZabbixSender("zabbix-server", 10051) - -# --- Adding data to send later --- -# Host, Key, Value are all necessary -# z.addData("test_host","test_trap","12") -# -# Optionally you can provide a specific timestamp for the sample -# z.addData("test_host","test_trap","13",1365787627) -# -# If you provide no timestamp, you still can assign one when sending, or let -# zabbix server to put the timestamp when the message is received. - -# --- Printing values --- -# Not that useful, but if you would like to see your data in tuple form: -# z.printData() - -# --- Sending data --- -# -# Just sending a single data point (you don't need to call add_value for this -# to work): -# z.sendSingle("test_host","test_trap","12") -# -# Sending everything at once, with no concern about -# individual item failure - -# -# result = z.sendData() -# for r in result: -# print "Result: %s -> %s" % (str(r[0]), r[1]) -# -# If you're ok with the result, you can delete the data inside the sender, to -# allow a new round of data feed/send. -# z.clearData() -# -# If you want to specify a timestamp to all values without one, you can specify -# the packet_clock parameter: -# z.sendData(packet_clock=1365787627) -# -# When you're sending data over a slow connection, you may find useful the -# possibility to send data in packets with no more than max_data_per_conn -# data points on it. -# All the data will be sent, but in smaller packets. -# For example, if you want to send 4000 data points in packets containing no -# more than 200 of them: -# -# results = z.sendData(max_data_per_conn=200) -# for partial_result in results: -# print partial_result -# -# Sending every item individually so that we can capture -# success or failure -# -# results = z.sendDataOneByOne() -# for (code,data) in results: -# if code != z.RC_OK: -# print "Failed to send: %s" % str(data) -# -# -##################################### -# Mini example of a working program # -##################################### -# -# import sys -# sys.path.append("/path/to/pyZabbixSender.py") -# from pyZabbixSender import pyZabbixSender -# -# z = pyZabbixSender() # Defaults to using ZABBIX_SERVER, ZABBIX_PORT -# z.addData("test_host","test_trap_1","12") -# z.addData("test_host","test_trap_2","13",1366033479) -# -# Two ways of printing internal data -# z.printData() -# print z -# -# results = z.sendDataOneByOne() -# for (code,data) in results: -# if code != z.RC_OK: -# print "Failed to send: %s" % str(data) -# z.clearData() -##################################### From 65a79efe55aabe4fad561e06aacbc6e3d4521b3b Mon Sep 17 00:00:00 2001 From: Vsevolod Novikov Date: Wed, 16 Mar 2016 17:43:24 +0300 Subject: [PATCH 11/17] Sync/Async fully compatible versions --- .gitignore | 16 ++++++++++++++++ pyZabbixSender/__init__.py | 1 + .../pyZabbixSender.py | 0 .../pyZabbixSenderBase.py | 0 syZabbixSender.py => pyZabbixSender/sy.py | 0 txZabbixSender.py => pyZabbixSender/tx.py | 0 setup.py | 9 ++------- test_seva.py | 2 +- test_seva_tx.py | 2 +- 9 files changed, 21 insertions(+), 9 deletions(-) create mode 100644 .gitignore create mode 100644 pyZabbixSender/__init__.py rename pyZabbixSender.py => pyZabbixSender/pyZabbixSender.py (100%) rename pyZabbixSenderBase.py => pyZabbixSender/pyZabbixSenderBase.py (100%) rename syZabbixSender.py => pyZabbixSender/sy.py (100%) rename txZabbixSender.py => pyZabbixSender/tx.py (100%) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8b34402 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +*.log +*.pot +*.pyc +*.swp + +.vagrant +.grunt + +# extra +.idea +.DS_Store +._* +*~ +*.egg-info +build +dist diff --git a/pyZabbixSender/__init__.py b/pyZabbixSender/__init__.py new file mode 100644 index 0000000..02a7152 --- /dev/null +++ b/pyZabbixSender/__init__.py @@ -0,0 +1 @@ +from pyZabbixSender import pyZabbixSender diff --git a/pyZabbixSender.py b/pyZabbixSender/pyZabbixSender.py similarity index 100% rename from pyZabbixSender.py rename to pyZabbixSender/pyZabbixSender.py diff --git a/pyZabbixSenderBase.py b/pyZabbixSender/pyZabbixSenderBase.py similarity index 100% rename from pyZabbixSenderBase.py rename to pyZabbixSender/pyZabbixSenderBase.py diff --git a/syZabbixSender.py b/pyZabbixSender/sy.py similarity index 100% rename from syZabbixSender.py rename to pyZabbixSender/sy.py diff --git a/txZabbixSender.py b/pyZabbixSender/tx.py similarity index 100% rename from txZabbixSender.py rename to pyZabbixSender/tx.py diff --git a/setup.py b/setup.py index a25e8d3..93a774b 100755 --- a/setup.py +++ b/setup.py @@ -8,19 +8,14 @@ def read(fname): setupconf = dict( name="pyZabbixSender", - py_modules=[ - "txZabbixSender", - "pyZabbixSender", - "pyZabbixSenderBase", - ], author="Kurt Momberg", author_email="kurtqm@yahoo.com.ar", description="Python implementation of zabbix_sender.", long_description = read('README.md'), url="https://github.com/kmomberg/pyZabbixSender", - version="0.1", + version="0.2", license = "GNU GPL v2", -# packages = find_packages(), + packages = find_packages(), # install_requires = [???], classifiers = [ "Operating System :: OS Independent", diff --git a/test_seva.py b/test_seva.py index 2e956e2..a2f75d8 100644 --- a/test_seva.py +++ b/test_seva.py @@ -1,4 +1,4 @@ -from syZabbixSender import syZabbixSender +from pyZabbixSender.sy import syZabbixSender # this import is optional. Here is used to create a timestamp to associate # to some data points, for example/testing purposes only. diff --git a/test_seva_tx.py b/test_seva_tx.py index 2ae10af..9ff4aa3 100644 --- a/test_seva_tx.py +++ b/test_seva_tx.py @@ -1,4 +1,4 @@ -from txZabbixSender import txZabbixSender +from pyZabbixSender.tx import txZabbixSender # this import is optional. Here is used to create a timestamp to associate # to some data points, for example/testing purposes only. From 76d9ac395846617b7a312230fabe7b1160911da3 Mon Sep 17 00:00:00 2001 From: Vsevolod Novikov Date: Wed, 16 Mar 2016 17:48:35 +0300 Subject: [PATCH 12/17] Sync/Async radme fixed --- README.md | 47 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 4de251b..8c9bb5f 100644 --- a/README.md +++ b/README.md @@ -26,13 +26,13 @@ pip install "git+git@github.com:baseride/pyZabbixSender.git" Usage ----- -Source code contains samples and comments to allows you start using it in no time. Here's a small example: +Here's a small example (synchronous code): ```python -from pyZabbixSender import pyZabbixSender +from pyZabbixSender.sy import syZabbixSender # Creating a sender object -z = pyZabbixSender(server="zabbix-server", port=10051) +z = syZabbixSender(server="zabbix-server", port=10051) # Adding data (without timestamp) z.addData(hostname="test_host", key="test_trap_1", value="12") @@ -45,8 +45,10 @@ z.addData("test_host", "test_trap_2", "2.43", 1365787627) results = z.sendData() # Check if everything was sent as expected -if results[0][0] != z.RC_OK: - print "oops!" +if not results[0][0]: + print "oops! Sending data has been failed" +elif results[0][1]['parsed']['processed'] != 3: + print "oops! Zabbix doesn't recognize passed identities" # Clear internal data to start populating again z.clearData() @@ -55,10 +57,10 @@ z.clearData() z.sendSingle("test_host","test_trap","12") ``` -The asynchronous code looks mostly the same, except asynchronous calls to zend...() functions and result processing: +The asynchronous code looks mostly the same, except asynchronous calls to zend...() functions: ```python -from txZabbixSender import txZabbixSender +from pyZabbixSender.tx import txZabbixSender from twisted.internet import reactor, defer @defer.inlineCallbacks @@ -77,7 +79,7 @@ def test(): results = yield z.sendData() # NOTE an asynchronous call # Check if everything was sent as expected - if not results[0][0]: # NOTE the asynchronous call returns a slightly dirrerent structure + if not results[0][0]: print "oops! Sending data has been failed" elif results[0][1]['parsed']['processed'] != 3: print "oops! Zabbix doesn't recognize passed identities" @@ -89,6 +91,35 @@ def test(): yield z.sendSingle("test_host","test_trap","12") # NOTE an asynchronous call ``` +The backward-compatible code looks mostly the same, except return value processing: + +```python +from pyZabbixSender import pyZabbixSender + +# Creating a sender object +z = pyZabbixSender(server="zabbix-server", port=10051) + +# Adding data (without timestamp) +z.addData(hostname="test_host", key="test_trap_1", value="12") +z.addData("test_host", "test_trap_2", "2.43") + +# Adding data (with timestamp) +z.addData("test_host", "test_trap_2", "2.43", 1365787627) + +# Ready to send your data? +results = z.sendData() + +# Check if everything was sent as expected +if results[0][0] != z.RC_OK: + print "oops!" + +# Clear internal data to start populating again +z.clearData() + +# Wants to send a single data point right now? +z.sendSingle("test_host","test_trap","12") +``` + There are some more options, so take a look at the [wiki] page and discover how easy is to use it ;) From ee2e5018ece10a2d45a36cabbf9d3752fdc7e5ab Mon Sep 17 00:00:00 2001 From: Vsevolod Novikov Date: Wed, 16 Mar 2016 17:49:36 +0300 Subject: [PATCH 13/17] Readme fixed --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8c9bb5f..1017b92 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ z.clearData() z.sendSingle("test_host","test_trap","12") ``` -The asynchronous code looks mostly the same, except asynchronous calls to zend...() functions: +The asynchronous code looks mostly the same, except asynchronous calls to send...() functions: ```python from pyZabbixSender.tx import txZabbixSender From 144e5bc76dc15dc3e3908bbe6b6ccd0c08ec7bf0 Mon Sep 17 00:00:00 2001 From: Vsevolod Novikov Date: Wed, 16 Mar 2016 18:01:35 +0300 Subject: [PATCH 14/17] Fixed json deps --- pyZabbixSender/tx.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pyZabbixSender/tx.py b/pyZabbixSender/tx.py index 2e1e2b0..81ce5ac 100644 --- a/pyZabbixSender/tx.py +++ b/pyZabbixSender/tx.py @@ -20,11 +20,6 @@ import sys import re -# If you're using an old version of python that don't have json available, -# you can use simplejson instead: https://simplejson.readthedocs.org/en/latest/ -#import simplejson as json -import json - from pyZabbixSenderBase import * class SenderProtocol(protocol.Protocol): From a347c8ca79ddaeced26b2d03649bea8142f4ed40 Mon Sep 17 00:00:00 2001 From: Vsevolod Novikov Date: Wed, 16 Mar 2016 18:06:26 +0300 Subject: [PATCH 15/17] Fixed installation instructions in readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1017b92..124d4d0 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,11 @@ Installation Install the package using a pip from the original repository: ```bash -pip install "git+git@github.com:kmomberg/pyZabbixSender.git" +pip install "git+git://github.com/kmomberg/pyZabbixSender.git" ``` or from one of the mirrors, like: ```bash -pip install "git+git@github.com:baseride/pyZabbixSender.git" +pip install "git+git://github.com/baseride/pyZabbixSender.git" ``` Usage From 7314cb6c64e6a5a74c5b078627d88a72b87076b4 Mon Sep 17 00:00:00 2001 From: Vsevolod Novikov Date: Wed, 16 Mar 2016 18:25:14 +0300 Subject: [PATCH 16/17] Removed temp.files --- test_seva.py | 126 ------------------------------------------------ test_seva_tx.py | 107 ---------------------------------------- 2 files changed, 233 deletions(-) delete mode 100644 test_seva.py delete mode 100644 test_seva_tx.py diff --git a/test_seva.py b/test_seva.py deleted file mode 100644 index a2f75d8..0000000 --- a/test_seva.py +++ /dev/null @@ -1,126 +0,0 @@ -from pyZabbixSender.sy import syZabbixSender - -# this import is optional. Here is used to create a timestamp to associate -# to some data points, for example/testing purposes only. -import time - -# Specifying server, but using default port -z = syZabbixSender("95.79.44.111") - -def printBanner(text): - border_char = '#' - border = border_char * (len(text) + 4) - print "\n\n%s" % border - print "%s %s %s" % (border_char, text, border_char) - print border - - -def test_01(): - ''' - Simple "debugging" usage example (using "sendDataOneByOne" method) - ''' - printBanner("test_01") - - # Just ensuring we start without data, in case other example was - # executed before this one - z.clearData() - - # Adding a host/trap that exist - z.addData("local.seva.test", "test", 21) - - # Adding a host that exist, but a trap that doesn't - z.addData("local.seva.test", "test1", 100) - - # Sending stored data, one by one, to know which host/traps have problems - results = z.sendDataOneByOne() - - # You'll get a "results" like: - #[ (0, {'host': 'test_host', 'value': 21, 'key': 'test_trap'}) - # (1, {'host': 'test_host', 'value': 100, 'key': 'test_trap1'}) - #] - print "---- Results content:" - print results - -def test_02(): - ''' - Testing "max_data_per_conn" parameter in "sendData" method - ''' - printBanner("test_02") - - # Just ensuring we start without data, in case other example was - # executed before this one - z.clearData() - - # Adding some valid data - for i in range (10): - z.addData("local.seva.test", "test", i) - - # Now adding a trap that doesn't exist in the server - z.addData("local.seva.test", "test1", 3) - - results = z.sendData(max_data_per_conn=3) - print "---- Results content:" - print results - - -def test_03(): - ''' - Testing method "sendSingle" - ''' - printBanner("test_03") - - # We don't need to clean internal data, because we'll send data given to the method - - # Sending data right now, without timestamp - result = z.sendSingle("local.seva.test", "test", 1) - - print "---- After sendSingle without timestamp" - print result - - # Now sending data with timestamp - result = z.sendSingle("local.seva.test", "test", 1, int(round(time.time()))) - print "\n---- After sendSingle with timestamp" - print result - - -def test_04(): - ''' - Testing getData method. - ''' - printBanner("test_04") - - # Just ensuring we start without data, in case other example was - # executed before this one - z.clearData() - - # Adding data - z.addData("local.seva.test", "test", 1) - z.addData("local.seva.test", "test", 2) - - # Showing current data - print "---- Showing stored data:" - print z - - # remember that getData returns a copy of the data - copy_of_data = z.getData() - print "\n---- Showing data returned:" - print copy_of_data - - # We'll modify returned data, to show this won't affect internal data - print "\n---- Modifying returned data" - copy_of_data.append({'host': 'local.seva.test', 'value': 500, 'key': 'test'}) - - # Showing current data - print "\n---- Showing stored data again (note is the same as before):" - print z - - print "\n---- Showing returned and modified data:" - print copy_of_data - - - -# Here you can execute the test/examples you want -test_01() -test_02() -test_03() -test_04() \ No newline at end of file diff --git a/test_seva_tx.py b/test_seva_tx.py deleted file mode 100644 index 9ff4aa3..0000000 --- a/test_seva_tx.py +++ /dev/null @@ -1,107 +0,0 @@ -from pyZabbixSender.tx import txZabbixSender - -# this import is optional. Here is used to create a timestamp to associate -# to some data points, for example/testing purposes only. -import time -import sys - -from twisted.internet import reactor, defer - -from twisted.python import log -log.startLogging(sys.stdout,False) - -# Specifying server, but using default port -z = txZabbixSender("95.79.44.111") - -def printBanner(text): - border_char = '#' - border = border_char * (len(text) + 4) - print "\n\n%s" % border - print "%s %s %s" % (border_char, text, border_char) - print border - - -@defer.inlineCallbacks -def tests(): - - - yield test_01() - yield test_02() - yield test_03() - reactor.stop() - -@defer.inlineCallbacks -def test_01(): - ''' - Simple "debugging" usage example (using "sendDataOneByOne" method) - ''' - printBanner("test_01") - - # Just ensuring we start without data, in case other example was - # executed before this one - z.clearData() - - # Adding a host/trap that exist - z.addData("local.seva.test", "test", 21) - - # Adding a host that exist, but a trap that doesn't - z.addData("local.seva.test", "test1", 100) - - # Sending stored data, one by one, to know which host/traps have problems - results = yield z.sendDataOneByOne() - - # You'll get a "results", note that they differ from sync code! - print "---- Results content:" - print results - -@defer.inlineCallbacks -def test_02(): - ''' - Testing "max_data_per_conn" parameter in "sendData" method - ''' - printBanner("test_02") - - # Just ensuring we start without data, in case other example was - # executed before this one - z.clearData() - - # Adding some valid data - for i in range (10): - z.addData("local.seva.test", "test", i) - - # Now adding a trap that doesn't exist in the server - z.addData("local.seva.test", "test1", 3) - - results = yield z.sendData(max_data_per_conn=3) - - # Now lets take a look at the return. - # You'll get a "results", note that they differ from sync code! - print "---- Results content:" - print results - - -@defer.inlineCallbacks -def test_03(): - ''' - Testing method "sendSingle" - ''' - printBanner("test_03") - - # We don't need to clean internal data, because we'll send data given to the method - - # Sending data right now, without timestamp - result = yield z.sendSingle("local.seva.test", "test", 1) - - print "---- After sendSingle without timestamp" - print result - - # Now sending data with timestamp - result = yield z.sendSingle("local.seva.test", "test", 1, int(round(time.time()))) - print "\n---- After sendSingle with timestamp" - print result - - - -# Here you can execute the test/examples you want -reactor.callWhenRunning(tests) -reactor.run() From 20f625c65b3a367d81feb13c5bd55f3f8886105e Mon Sep 17 00:00:00 2001 From: Vsevolod Novikov Date: Wed, 16 Mar 2016 18:29:33 +0300 Subject: [PATCH 17/17] Fixed readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 124d4d0..2e49832 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ results = z.sendData() # Check if everything was sent as expected if not results[0][0]: print "oops! Sending data has been failed" -elif results[0][1]['parsed']['processed'] != 3: +elif results[0][1]['info']['processed'] != 3: print "oops! Zabbix doesn't recognize passed identities" # Clear internal data to start populating again @@ -81,7 +81,7 @@ def test(): # Check if everything was sent as expected if not results[0][0]: print "oops! Sending data has been failed" - elif results[0][1]['parsed']['processed'] != 3: + elif results[0][1]['info']['processed'] != 3: print "oops! Zabbix doesn't recognize passed identities" # Clear internal data to start populating again