├── .coveragerc ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.rst ├── docs ├── Makefile └── source │ ├── api.rst │ ├── conf.py │ ├── index.rst │ ├── quickstart_guide.rst │ └── sender.rst ├── pyzabbix ├── __init__.py ├── api.py ├── logger.py ├── sender.py └── version.py ├── setup.py ├── tests ├── __init__.py ├── data │ ├── Zabbix2 │ │ ├── Dockerfile │ │ ├── docker-compose.yml │ │ └── zabbix.sql │ ├── Zabbix3 │ │ ├── Dockerfile │ │ ├── docker-compose.yml │ │ └── zabbix.sql │ ├── default.conf │ ├── ssl │ │ ├── nginx.crt │ │ └── nginx.key │ └── zabbix_agentd.conf ├── test_Functional_API.py ├── test_Functional_API_Old.py ├── test_Functional_Sender.py ├── test_Functional_Sender_Old.py ├── test_api.py ├── test_hide_sensitive_filter.py └── test_sender.py └── zabbix ├── __init__.py ├── api.py └── sender.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = 3 | */python?.?/* 4 | */lib-python/?.?/*.py 5 | */site-packages/* 6 | *.egg/* 7 | tests/* 8 | pyzabbix/logger.py 9 | [report] 10 | exclude_lines = 11 | import ConfigParser as configparser 12 | logger 13 | break 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | 3 | # editors 4 | .vscode 5 | .idea 6 | 7 | # Byte-compiled / optimized / DLL files 8 | __pycache__/ 9 | *.py[cod] 10 | *$py.class 11 | 12 | # C extensions 13 | *.so 14 | 15 | # Distribution / packaging 16 | .Python 17 | build/ 18 | develop-eggs/ 19 | dist/ 20 | downloads/ 21 | eggs/ 22 | .eggs/ 23 | lib/ 24 | lib64/ 25 | parts/ 26 | sdist/ 27 | var/ 28 | wheels/ 29 | pip-wheel-metadata/ 30 | share/python-wheels/ 31 | *.egg-info/ 32 | .installed.cfg 33 | *.egg 34 | MANIFEST 35 | 36 | # PyInstaller 37 | # Usually these files are written by a python script from a template 38 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 39 | *.manifest 40 | *.spec 41 | 42 | # Installer logs 43 | pip-log.txt 44 | pip-delete-this-directory.txt 45 | 46 | # Unit test / coverage reports 47 | htmlcov/ 48 | .tox/ 49 | .nox/ 50 | .coverage 51 | .coverage.* 52 | .cache 53 | nosetests.xml 54 | coverage.xml 55 | *.cover 56 | .hypothesis/ 57 | .pytest_cache/ 58 | 59 | 60 | # pyenv 61 | .python-version 62 | 63 | # Environments 64 | .env 65 | .venv 66 | env/ 67 | venv/ 68 | ENV/ 69 | env.bak/ 70 | venv.bak/ 71 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial 2 | 3 | sudo: required 4 | 5 | services: 6 | - docker 7 | 8 | language: python 9 | 10 | python: 11 | - "2.7" 12 | - "3.4" 13 | - "3.5" 14 | - "3.6" 15 | - "3.7" 16 | 17 | env: 18 | - ZABBIX_VERSION: 2 19 | - ZABBIX_VERSION: 3 20 | 21 | before_install: 22 | - curl -L https://github.com/docker/compose/releases/download/1.4.1/docker-compose-`uname -s`-`uname -m` > docker-compose 23 | - chmod +x docker-compose 24 | - sudo mv docker-compose /usr/local/bin 25 | - pushd tests/data/Zabbix${ZABBIX_VERSION} && docker-compose up -d 26 | - popd 27 | 28 | install: 29 | - pip install coveralls pycodestyle 30 | 31 | script: 32 | - while ! nc -z 127.0.0.1 10051; do sleep 1; done 33 | - sleep 60 34 | - pycodestyle --ignore=E501,W504 pyzabbix/ 35 | - pycodestyle tests/*.py 36 | - coverage run setup.py test 37 | 38 | after_success: 39 | coveralls 40 | 41 | notifications: 42 | email: 43 | - alexey.dubkov@gmail.com 44 | - maksim77ster@gmail.com 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | |Build Status| |Coverage| |PyPi status| |PyPi version| 2 | 3 | Zabbix module for Python 4 | ======================== 5 | 6 | Install 7 | ------- 8 | 9 | You can install Zabbix modules for Python with pip: 10 | 11 | :: 12 | 13 | pip install py-zabbix 14 | 15 | Official documentation for `py-zabbix `__ 16 | -------------------------------------------------------------------------------------- 17 | 18 | Examples 19 | -------- 20 | 21 | ZabbixAPI 22 | ~~~~~~~~~ 23 | 24 | .. code:: python 25 | 26 | from pyzabbix.api import ZabbixAPI 27 | 28 | # Create ZabbixAPI class instance 29 | zapi = ZabbixAPI(url='https://localhost/zabbix/', user='Admin', password='zabbix') 30 | 31 | # Get all monitored hosts 32 | result1 = zapi.host.get(monitored_hosts=1, output='extend') 33 | 34 | # Get all disabled hosts 35 | result2 = zapi.do_request('host.get', 36 | { 37 | 'filter': {'status': 1}, 38 | 'output': 'extend' 39 | }) 40 | 41 | # Filter results 42 | hostnames1 = [host['host'] for host in result1] 43 | hostnames2 = [host['host'] for host in result2['result']] 44 | 45 | # Logout from Zabbix 46 | zapi.user.logout() 47 | 48 | Or use 'with' statement to logout automatically: 49 | 50 | .. code:: python 51 | 52 | from pyzabbix.api import ZabbixAPI 53 | 54 | # Create ZabbixAPI class instance 55 | with ZabbixAPI(url='https://localhost/zabbix/', user='Admin', password='zabbix') as zapi: 56 | 57 | # Get all monitored hosts 58 | result1 = zapi.host.get(monitored_hosts=1, output='extend') 59 | 60 | Enable logging: 61 | 62 | .. code:: python 63 | 64 | import sys 65 | import logging 66 | from pyzabbix.api import ZabbixAPI 67 | 68 | # Create ZabbixAPI class instance 69 | logger = logging.getLogger("pyzabbix") 70 | logger.setLevel(logging.DEBUG) 71 | handler = logging.StreamHandler(sys.stdout) 72 | logger.addHandler(handler) 73 | 74 | zapi = ZabbixAPI(url='http://localhost', user='Admin', password='zabbix') 75 | 76 | Note that passwords and auth tokens are hidden when raw messages are logged or raised in exceptions ( but not hidden if print() is used): 77 | 78 | .. code:: python 79 | 80 | ZabbixAPI.login(Admin,********) 81 | Call user.login method 82 | urllib2.Request(http://localhost/api_jsonrpc.php, {"jsonrpc": "2.0", "method": "user.login", "params": {"user": "Admin", "password": "********"}, "id": "1"}) 83 | Response Body: { 84 | "jsonrpc": "2.0", 85 | "result": "********", 86 | "id": "1" 87 | } 88 | 89 | 90 | ZabbixSender 91 | ~~~~~~~~~~~~ 92 | 93 | .. code:: python 94 | 95 | from pyzabbix import ZabbixMetric, ZabbixSender 96 | 97 | # Send metrics to zabbix trapper 98 | packet = [ 99 | ZabbixMetric('hostname1', 'test[cpu_usage]', 2), 100 | ZabbixMetric('hostname1', 'test[system_status]', "OK"), 101 | ZabbixMetric('hostname1', 'test[disk_io]', '0.1'), 102 | ZabbixMetric('hostname1', 'test[cpu_usage]', 20, 1411598020), 103 | ] 104 | 105 | result = ZabbixSender(use_config=True).send(packet) 106 | 107 | .. |Build Status| image:: https://travis-ci.org/adubkov/py-zabbix.svg?branch=master 108 | :target: https://travis-ci.org/adubkov/py-zabbix 109 | .. |Coverage| image:: https://coveralls.io/repos/github/adubkov/py-zabbix/badge.svg?branch=master 110 | :target: https://coveralls.io/github/adubkov/py-zabbix?branch=master 111 | .. |PyPi status| image:: https://img.shields.io/pypi/status/py-zabbix.svg 112 | :target: https://pypi.python.org/pypi/py-zabbix/ 113 | .. |PyPi version| image:: https://img.shields.io/pypi/v/py-zabbix.svg 114 | :target: https://pypi.python.org/pypi/py-zabbix/ 115 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 21 | 22 | .PHONY: help 23 | help: 24 | @echo "Please use \`make ' where is one of" 25 | @echo " html to make standalone HTML files" 26 | @echo " dirhtml to make HTML files named index.html in directories" 27 | @echo " singlehtml to make a single large HTML file" 28 | @echo " pickle to make pickle files" 29 | @echo " json to make JSON files" 30 | @echo " htmlhelp to make HTML files and a HTML help project" 31 | @echo " qthelp to make HTML files and a qthelp project" 32 | @echo " applehelp to make an Apple Help Book" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | @echo " coverage to run coverage check of the documentation (if enabled)" 49 | 50 | .PHONY: clean 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | .PHONY: html 55 | html: 56 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 57 | @echo 58 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 59 | 60 | .PHONY: dirhtml 61 | dirhtml: 62 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 63 | @echo 64 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 65 | 66 | .PHONY: singlehtml 67 | singlehtml: 68 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 69 | @echo 70 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 71 | 72 | .PHONY: pickle 73 | pickle: 74 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 75 | @echo 76 | @echo "Build finished; now you can process the pickle files." 77 | 78 | .PHONY: json 79 | json: 80 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 81 | @echo 82 | @echo "Build finished; now you can process the JSON files." 83 | 84 | .PHONY: htmlhelp 85 | htmlhelp: 86 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 87 | @echo 88 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 89 | ".hhp project file in $(BUILDDIR)/htmlhelp." 90 | 91 | .PHONY: qthelp 92 | qthelp: 93 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 94 | @echo 95 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 96 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 97 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/py-zabbix.qhcp" 98 | @echo "To view the help file:" 99 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/py-zabbix.qhc" 100 | 101 | .PHONY: applehelp 102 | applehelp: 103 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 104 | @echo 105 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 106 | @echo "N.B. You won't be able to view it unless you put it in" \ 107 | "~/Library/Documentation/Help or install it in your application" \ 108 | "bundle." 109 | 110 | .PHONY: devhelp 111 | devhelp: 112 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 113 | @echo 114 | @echo "Build finished." 115 | @echo "To view the help file:" 116 | @echo "# mkdir -p $$HOME/.local/share/devhelp/py-zabbix" 117 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/py-zabbix" 118 | @echo "# devhelp" 119 | 120 | .PHONY: epub 121 | epub: 122 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 123 | @echo 124 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 125 | 126 | .PHONY: latex 127 | latex: 128 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 129 | @echo 130 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 131 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 132 | "(use \`make latexpdf' here to do that automatically)." 133 | 134 | .PHONY: latexpdf 135 | latexpdf: 136 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 137 | @echo "Running LaTeX files through pdflatex..." 138 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 139 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 140 | 141 | .PHONY: latexpdfja 142 | latexpdfja: 143 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 144 | @echo "Running LaTeX files through platex and dvipdfmx..." 145 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 146 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 147 | 148 | .PHONY: text 149 | text: 150 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 151 | @echo 152 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 153 | 154 | .PHONY: man 155 | man: 156 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 157 | @echo 158 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 159 | 160 | .PHONY: texinfo 161 | texinfo: 162 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 163 | @echo 164 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 165 | @echo "Run \`make' in that directory to run these through makeinfo" \ 166 | "(use \`make info' here to do that automatically)." 167 | 168 | .PHONY: info 169 | info: 170 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 171 | @echo "Running Texinfo files through makeinfo..." 172 | make -C $(BUILDDIR)/texinfo info 173 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 174 | 175 | .PHONY: gettext 176 | gettext: 177 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 178 | @echo 179 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 180 | 181 | .PHONY: changes 182 | changes: 183 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 184 | @echo 185 | @echo "The overview file is in $(BUILDDIR)/changes." 186 | 187 | .PHONY: linkcheck 188 | linkcheck: 189 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 190 | @echo 191 | @echo "Link check complete; look for any errors in the above output " \ 192 | "or in $(BUILDDIR)/linkcheck/output.txt." 193 | 194 | .PHONY: doctest 195 | doctest: 196 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 197 | @echo "Testing of doctests in the sources finished, look at the " \ 198 | "results in $(BUILDDIR)/doctest/output.txt." 199 | 200 | .PHONY: coverage 201 | coverage: 202 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 203 | @echo "Testing of coverage in the sources finished, look at the " \ 204 | "results in $(BUILDDIR)/coverage/python.txt." 205 | 206 | .PHONY: xml 207 | xml: 208 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 209 | @echo 210 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 211 | 212 | .PHONY: pseudoxml 213 | pseudoxml: 214 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 215 | @echo 216 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 217 | -------------------------------------------------------------------------------- /docs/source/api.rst: -------------------------------------------------------------------------------- 1 | .. _pyzabbix.api: 2 | 3 | =================== 4 | Module `pyzabbix.api` 5 | =================== 6 | 7 | This module provide classes to work with Zabbix API. 8 | 9 | .. automodule:: pyzabbix.api 10 | :members: 11 | :exclude-members: ZabbixAPIException, ZabbixAPIObjectClass 12 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # py-zabbix documentation build configuration file, created by 4 | # sphinx-quickstart on Sat Mar 19 03:04:03 2016. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | 18 | # If extensions (or modules to document with autodoc) are in another directory, 19 | # add these directories to sys.path here. If the directory is relative to the 20 | # documentation root, use os.path.abspath to make it absolute, like shown here. 21 | #sys.path.insert(0, os.path.abspath('.')) 22 | 23 | # -- General configuration ------------------------------------------------ 24 | 25 | # If your documentation needs a minimal Sphinx version, state it here. 26 | #needs_sphinx = '1.0' 27 | 28 | # Add any Sphinx extension module names here, as strings. They can be 29 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 30 | # ones. 31 | extensions = [ 32 | 'sphinx.ext.autodoc', 33 | 'sphinx.ext.doctest', 34 | 'sphinx.ext.coverage', 35 | ] 36 | 37 | # Add any paths that contain templates here, relative to this directory. 38 | templates_path = ['_templates'] 39 | 40 | # The suffix(es) of source filenames. 41 | # You can specify multiple suffix as a list of string: 42 | # source_suffix = ['.rst', '.md'] 43 | source_suffix = '.rst' 44 | 45 | # The encoding of source files. 46 | #source_encoding = 'utf-8-sig' 47 | 48 | # The master toctree document. 49 | master_doc = 'index' 50 | 51 | # General information about the project. 52 | project = u'py-zabbix' 53 | copyright = u'2016, Alexey Dubkov' 54 | author = u'Alexey Dubkov' 55 | 56 | # The version info for the project you're documenting, acts as replacement for 57 | # |version| and |release|, also used in various other places throughout the 58 | # built documents. 59 | # 60 | # The short X.Y version. 61 | version = u'6.3' 62 | # The full version, including alpha/beta/rc tags. 63 | release = u'6.3' 64 | 65 | # The language for content autogenerated by Sphinx. Refer to documentation 66 | # for a list of supported languages. 67 | # 68 | # This is also used if you do content translation via gettext catalogs. 69 | # Usually you set "language" from the command line for these cases. 70 | language = None 71 | 72 | # There are two options for replacing |today|: either, you set today to some 73 | # non-false value, then it is used: 74 | #today = '' 75 | # Else, today_fmt is used as the format for a strftime call. 76 | #today_fmt = '%B %d, %Y' 77 | 78 | # List of patterns, relative to source directory, that match files and 79 | # directories to ignore when looking for source files. 80 | exclude_patterns = [] 81 | 82 | # The reST default role (used for this markup: `text`) to use for all 83 | # documents. 84 | #default_role = None 85 | 86 | # If true, '()' will be appended to :func: etc. cross-reference text. 87 | #add_function_parentheses = True 88 | 89 | # If true, the current module name will be prepended to all description 90 | # unit titles (such as .. function::). 91 | #add_module_names = True 92 | 93 | # If true, sectionauthor and moduleauthor directives will be shown in the 94 | # output. They are ignored by default. 95 | #show_authors = False 96 | 97 | # The name of the Pygments (syntax highlighting) style to use. 98 | pygments_style = 'sphinx' 99 | 100 | # A list of ignored prefixes for module index sorting. 101 | #modindex_common_prefix = [] 102 | 103 | # If true, keep warnings as "system message" paragraphs in the built documents. 104 | #keep_warnings = False 105 | 106 | # If true, `todo` and `todoList` produce output, else they produce nothing. 107 | todo_include_todos = False 108 | 109 | 110 | # -- Options for HTML output ---------------------------------------------- 111 | 112 | # The theme to use for HTML and HTML Help pages. See the documentation for 113 | # a list of builtin themes. 114 | html_theme = 'sphinx_rtd_theme' 115 | 116 | # Theme options are theme-specific and customize the look and feel of a theme 117 | # further. For a list of options available for each theme, see the 118 | # documentation. 119 | #html_theme_options = {} 120 | 121 | # Add any paths that contain custom themes here, relative to this directory. 122 | #html_theme_path = [] 123 | 124 | # The name for this set of Sphinx documents. If None, it defaults to 125 | # " v documentation". 126 | #html_title = None 127 | 128 | # A shorter title for the navigation bar. Default is the same as html_title. 129 | #html_short_title = None 130 | 131 | # The name of an image file (relative to this directory) to place at the top 132 | # of the sidebar. 133 | #html_logo = None 134 | 135 | # The name of an image file (relative to this directory) to use as a favicon of 136 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 137 | # pixels large. 138 | #html_favicon = None 139 | 140 | # Add any paths that contain custom static files (such as style sheets) here, 141 | # relative to this directory. They are copied after the builtin static files, 142 | # so a file named "default.css" will overwrite the builtin "default.css". 143 | html_static_path = ['_static'] 144 | 145 | # Add any extra paths that contain custom files (such as robots.txt or 146 | # .htaccess) here, relative to this directory. These files are copied 147 | # directly to the root of the documentation. 148 | #html_extra_path = [] 149 | 150 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 151 | # using the given strftime format. 152 | #html_last_updated_fmt = '%b %d, %Y' 153 | 154 | # If true, SmartyPants will be used to convert quotes and dashes to 155 | # typographically correct entities. 156 | #html_use_smartypants = True 157 | 158 | # Custom sidebar templates, maps document names to template names. 159 | #html_sidebars = {} 160 | 161 | # Additional templates that should be rendered to pages, maps page names to 162 | # template names. 163 | #html_additional_pages = {} 164 | 165 | # If false, no module index is generated. 166 | #html_domain_indices = True 167 | 168 | # If false, no index is generated. 169 | #html_use_index = True 170 | 171 | # If true, the index is split into individual pages for each letter. 172 | #html_split_index = False 173 | 174 | # If true, links to the reST sources are added to the pages. 175 | html_show_sourcelink = False 176 | 177 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 178 | #html_show_sphinx = True 179 | 180 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 181 | #html_show_copyright = True 182 | 183 | # If true, an OpenSearch description file will be output, and all pages will 184 | # contain a tag referring to it. The value of this option must be the 185 | # base URL from which the finished HTML is served. 186 | #html_use_opensearch = '' 187 | 188 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 189 | #html_file_suffix = None 190 | 191 | # Language to be used for generating the HTML full-text search index. 192 | # Sphinx supports the following languages: 193 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 194 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' 195 | #html_search_language = 'en' 196 | 197 | # A dictionary with options for the search language support, empty by default. 198 | # Now only 'ja' uses this config value 199 | #html_search_options = {'type': 'default'} 200 | 201 | # The name of a javascript file (relative to the configuration directory) that 202 | # implements a search results scorer. If empty, the default will be used. 203 | #html_search_scorer = 'scorer.js' 204 | 205 | # Output file base name for HTML help builder. 206 | htmlhelp_basename = 'py-zabbixdoc' 207 | 208 | # -- Options for LaTeX output --------------------------------------------- 209 | 210 | latex_elements = { 211 | # The paper size ('letterpaper' or 'a4paper'). 212 | #'papersize': 'letterpaper', 213 | 214 | # The font size ('10pt', '11pt' or '12pt'). 215 | #'pointsize': '10pt', 216 | 217 | # Additional stuff for the LaTeX preamble. 218 | #'preamble': '', 219 | 220 | # Latex figure (float) alignment 221 | #'figure_align': 'htbp', 222 | } 223 | 224 | # Grouping the document tree into LaTeX files. List of tuples 225 | # (source start file, target name, title, 226 | # author, documentclass [howto, manual, or own class]). 227 | latex_documents = [ 228 | (master_doc, 'py-zabbix.tex', u'py-zabbix Documentation', 229 | u'Alexey Dubkov', 'manual'), 230 | ] 231 | 232 | # The name of an image file (relative to this directory) to place at the top of 233 | # the title page. 234 | #latex_logo = None 235 | 236 | # For "manual" documents, if this is true, then toplevel headings are parts, 237 | # not chapters. 238 | #latex_use_parts = False 239 | 240 | # If true, show page references after internal links. 241 | #latex_show_pagerefs = False 242 | 243 | # If true, show URL addresses after external links. 244 | #latex_show_urls = False 245 | 246 | # Documents to append as an appendix to all manuals. 247 | #latex_appendices = [] 248 | 249 | # If false, no module index is generated. 250 | #latex_domain_indices = True 251 | 252 | 253 | # -- Options for manual page output --------------------------------------- 254 | 255 | # One entry per manual page. List of tuples 256 | # (source start file, name, description, authors, manual section). 257 | man_pages = [ 258 | (master_doc, 'py-zabbix', u'py-zabbix Documentation', 259 | [author], 1) 260 | ] 261 | 262 | # If true, show URL addresses after external links. 263 | #man_show_urls = False 264 | 265 | 266 | # -- Options for Texinfo output ------------------------------------------- 267 | 268 | # Grouping the document tree into Texinfo files. List of tuples 269 | # (source start file, target name, title, author, 270 | # dir menu entry, description, category) 271 | texinfo_documents = [ 272 | (master_doc, 'py-zabbix', u'py-zabbix Documentation', 273 | author, 'py-zabbix', 'One line description of project.', 274 | 'Miscellaneous'), 275 | ] 276 | 277 | # Documents to append as an appendix to all manuals. 278 | #texinfo_appendices = [] 279 | 280 | # If false, no module index is generated. 281 | #texinfo_domain_indices = True 282 | 283 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 284 | #texinfo_show_urls = 'footnote' 285 | 286 | # If true, do not generate a @detailmenu in the "Top" node's menu. 287 | #texinfo_no_detailmenu = False 288 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. py-zabbix documentation master file, created by 2 | sphinx-quickstart on Sat Mar 19 03:04:03 2016. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to py-zabbix's documentation! 7 | ===================================== 8 | 9 | ======================== 10 | Zabbix module for Python 11 | ======================== 12 | 13 | 14 | That set of modules allow to work with Zabbix in Python. 15 | 16 | Currently it contains: 17 | * :ref:`pyzabbix.api` for work with Zabbix API. 18 | * :ref:`pyzabbix.sender` for send metrics to Zabbix. 19 | 20 | .. note:: `py-zabbix` do not require addition modules, it use only standard 21 | Python modules. 22 | 23 | .. toctree:: 24 | :maxdepth: 1 25 | :hidden: 26 | 27 | quickstart_guide 28 | api 29 | sender 30 | 31 | .. autosummary:: 32 | zabbix 33 | 34 | Indices and tables 35 | ================== 36 | 37 | * :ref:`genindex` 38 | * :ref:`modindex` 39 | * :ref:`search` 40 | 41 | -------------------------------------------------------------------------------- /docs/source/quickstart_guide.rst: -------------------------------------------------------------------------------- 1 | .. _quickstart_guide: 2 | 3 | ################ 4 | Quickstart guide 5 | ################ 6 | 7 | ======= 8 | Install 9 | ======= 10 | 11 | You can install Zabbix modules for Python with pip: 12 | 13 | .. code-block:: bash 14 | 15 | pip install py-zabbix 16 | 17 | ======== 18 | Examples 19 | ======== 20 | 21 | --------- 22 | ZabbixAPI 23 | --------- 24 | 25 | You can make zabbix api call with two ways. 26 | 27 | 1. By With dynamicaly mapping :class:`pyzabbix.api.ZabbixAPI` methods on zabbix api: 28 | 29 | :: 30 | 31 | result = zapi.host.get(status=1) 32 | 33 | 2. By passing zabbix api function and arguments as parameters: 34 | 35 | :: 36 | 37 | result = zapi.do_request('host.get', {'status':1}) 38 | 39 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 40 | Get list of not monitored hosts from zabbix 41 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 42 | 43 | :: 44 | 45 | from pyzabbix import ZabbixAPI 46 | 47 | # Create ZabbixAPI class instance 48 | zapi = ZabbixAPI(url='https://localhost/zabbix/', user='Admin', password='zabbix') 49 | 50 | # Get all disabled hosts 51 | result1 = zapi.host.get(status=1) 52 | hostnames = [host['host'] for host in result1] 53 | 54 | # Other way to get all disabled hosts 55 | result2 = zapi.do_request('host.get', {'status':1}) 56 | hostnames2 = [host['host'] for host in result2['result']] 57 | 58 | 59 | ------------ 60 | ZabbixSender 61 | ------------ 62 | ~~~~~~~~~~~~~~~~~~~~~ 63 | Send metric to zabbix 64 | ~~~~~~~~~~~~~~~~~~~~~ 65 | 66 | :: 67 | 68 | metrics= [ZabbixMetric('localhost', 'cpu[usage]', '15.4')] 69 | ZabbixSender(use_config=True).send(metrics) 70 | 71 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 72 | Usage with zabbix_agentd.conf 73 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 74 | If your box already have zabbix_agent installed. Py-zabbix can use zabbix server 75 | from zabbix_agentd.conf file. 76 | 77 | :: 78 | 79 | from pyzabbix import ZabbixMetric, ZabbixSender 80 | 81 | packet = [ 82 | ZabbixMetric('centos', 'test[cpu_usage]', 2), 83 | ZabbixMetric('centos', 'test[system_status]', "OK"), 84 | ZabbixMetric('centos', 'test[disk_io]', '0.1'), 85 | ZabbixMetric('centos', 'test[cpu_usage]', 20, 1411598020), 86 | ZabbixMetric('centos', 'test[cpu_usage]', 30, 1411598120), 87 | ZabbixMetric('centos', 'test[cpu_usage]', 40, 1411598240), 88 | ] 89 | 90 | sender = ZabbixSender(use_config=True) 91 | sender.send(packet) 92 | -------------------------------------------------------------------------------- /docs/source/sender.rst: -------------------------------------------------------------------------------- 1 | .. _pyzabbix.sender: 2 | 3 | ====================== 4 | Module `pyzabbix.sender` 5 | ====================== 6 | 7 | .. automodule:: pyzabbix.sender 8 | :members: 9 | -------------------------------------------------------------------------------- /pyzabbix/__init__.py: -------------------------------------------------------------------------------- 1 | from .api import ZabbixAPI, ZabbixAPIException, ssl_context_compat 2 | from .sender import ZabbixMetric, ZabbixSender, ZabbixResponse 3 | -------------------------------------------------------------------------------- /pyzabbix/api.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | # 3 | # Copyright © 2014 Alexey Dubkov 4 | # 5 | # This file is part of py-zabbix. 6 | # 7 | # Py-zabbix is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Py-zabbix is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with py-zabbix. If not, see . 19 | 20 | import json 21 | import logging 22 | import os 23 | import ssl 24 | import sys 25 | import base64 26 | 27 | # For Python 2 and 3 compatibility 28 | try: 29 | import urllib2 30 | except ImportError: 31 | # Since Python 3, urllib2.Request and urlopen were moved to 32 | # the urllib.request. 33 | import urllib.request as urllib2 34 | 35 | from .version import __version__ 36 | from .logger import NullHandler, HideSensitiveFilter, HideSensitiveService 37 | 38 | null_handler = NullHandler() 39 | logger = logging.getLogger(__name__) 40 | logger.addHandler(null_handler) 41 | logger.addFilter(HideSensitiveFilter()) 42 | 43 | 44 | class ZabbixAPIException(Exception): 45 | """ZabbixAPI exception class. 46 | 47 | :code list: 48 | :32602: Invalid params (eg already exists) 49 | :32500: No permissions 50 | """ 51 | def __init__(self, *args): 52 | super(Exception, self).__init__(*args) 53 | if len(args) == 1 and isinstance(args[0], dict): 54 | self.error = args[0] 55 | self.error['json'] = HideSensitiveService.hide_sensitive(self.error['json']) 56 | self.message = self.error['message'] 57 | self.code = self.error['code'] 58 | self.data = self.error['data'] 59 | self.json = self.error['json'] 60 | 61 | 62 | class ZabbixAPIObjectClass(object): 63 | """ZabbixAPI Object class. 64 | 65 | :type group: str 66 | :param group: Zabbix API method group name. 67 | Example: `apiinfo.version` method it will be `apiinfo`. 68 | 69 | :type parent: :class:`zabbix.api.ZabbixAPI` object 70 | :param parent: ZabbixAPI object to use as parent. 71 | """ 72 | 73 | def __init__(self, group, parent): 74 | self.group = group 75 | self.parent = parent 76 | 77 | def __getattr__(self, name): 78 | """Dynamically create a method. 79 | 80 | :type name: str 81 | :param name: Zabbix API method name. 82 | Example: `apiinfo.version` method it will be `version`. 83 | """ 84 | 85 | def fn(*args, **kwargs): 86 | if args and kwargs: 87 | raise TypeError("Found both args and kwargs") 88 | 89 | method = '{0}.{1}'.format(self.group, name) 90 | logger.debug("Call %s method", method) 91 | 92 | return self.parent.do_request( 93 | method, 94 | args or kwargs 95 | )['result'] 96 | 97 | return fn 98 | 99 | 100 | def ssl_context_compat(func): 101 | def inner(req): 102 | # We should explicitly disable cert verification to support 103 | # self-signed certs with urllib2 since Python 2.7.9 and 3.4.3 104 | 105 | default_version = (2, 7, 9) 106 | version = { 107 | 2: default_version, 108 | 3: (3, 4, 3), 109 | } 110 | 111 | python_version = sys.version_info[0] 112 | minimum_version = version.get(python_version, default_version) 113 | 114 | if sys.version_info[0:3] >= minimum_version: 115 | # Create default context to skip SSL cert verification. 116 | ctx = ssl.create_default_context() 117 | ctx.check_hostname = False 118 | ctx.verify_mode = ssl.CERT_NONE 119 | res = func(req, context=ctx) 120 | else: 121 | res = func(req) 122 | 123 | return res 124 | 125 | return inner 126 | 127 | 128 | @ssl_context_compat 129 | def urlopen(*args, **kwargs): 130 | return urllib2.urlopen(*args, **kwargs) 131 | 132 | 133 | class ZabbixAPI(object): 134 | """ZabbixAPI class, implement interface to zabbix api. 135 | 136 | :type url: str 137 | :param url: URL to zabbix api. Default: `ZABBIX_URL` or 138 | `https://localhost/zabbix` 139 | 140 | :type use_authenticate: bool 141 | :param use_authenticate: Use `user.authenticate` method if `True` else 142 | `user.login`. 143 | 144 | :type use_basic_auth: bool 145 | :param use_basic_auth: Using basic auth if `True` 146 | 147 | :type user: str 148 | :param user: Zabbix user name. Default: `ZABBIX_USER` or `'Admin'`. 149 | 150 | :type password: str 151 | :param password: Zabbix user password. Default `ZABBIX_PASSWORD` or 152 | `zabbix`. 153 | 154 | >>> from pyzabbix import ZabbixAPI 155 | >>> z = ZabbixAPI('https://zabbix.server', user='Admin', password='zabbix') 156 | >>> # Get API Version 157 | >>> z.api_info.version() 158 | >>> u'2.2.1' 159 | >>> # or 160 | >>> z.do_request('apiinfo.version') 161 | >>> {u'jsonrpc': u'2.0', u'result': u'2.2.1', u'id': u'1'} 162 | >>> # Get all disabled hosts 163 | >>> z.host.get(status=1) 164 | >>> # or 165 | >>> z.do_request('host.getobjects', {'status': 1}) 166 | """ 167 | 168 | def __init__(self, url=None, use_authenticate=False, use_basic_auth=False, user=None, 169 | password=None): 170 | 171 | url = url or os.environ.get('ZABBIX_URL') or 'https://localhost/zabbix' 172 | user = user or os.environ.get('ZABBIX_USER') or 'Admin' 173 | password = password or os.environ.get('ZABBIX_PASSWORD') or 'zabbix' 174 | 175 | self.use_authenticate = use_authenticate 176 | self.use_basic_auth = use_basic_auth 177 | self.auth = None 178 | self.url = url + '/api_jsonrpc.php' 179 | self.base64_cred = self.cred_to_base64(user, password) if self.use_basic_auth else None 180 | self._login(user, password) 181 | logger.debug("JSON-PRC Server: %s", self.url) 182 | 183 | def __getattr__(self, name): 184 | """Dynamically create an object class (ie: host). 185 | 186 | :type name: str 187 | :param name: Zabbix API method group name. 188 | Example: `apiinfo.version` method it will be `apiinfo`. 189 | """ 190 | 191 | return ZabbixAPIObjectClass(name, self) 192 | 193 | def _login(self, user='', password=''): 194 | """Do login to zabbix server. 195 | 196 | :type user: str 197 | :param user: Zabbix user 198 | 199 | :type password: str 200 | :param password: Zabbix user password 201 | """ 202 | 203 | logger.debug("ZabbixAPI.login({0},{1})".format(user, HideSensitiveService.HIDEMASK)) 204 | 205 | self.auth = None 206 | 207 | if self.use_authenticate: 208 | self.auth = self.user.authenticate(user=user, password=password) 209 | else: 210 | self.auth = self.user.login(user=user, password=password) 211 | 212 | def _logout(self): 213 | """Do logout from zabbix server.""" 214 | 215 | if self.auth: 216 | logger.debug("ZabbixAPI.logout()") 217 | 218 | if self.user.logout(): 219 | self.auth = None 220 | 221 | def __enter__(self): 222 | return self 223 | 224 | def __exit__(self, *args): 225 | self._logout() 226 | 227 | @staticmethod 228 | def cred_to_base64(user, password): 229 | """Create header for basic authorization 230 | :type user: str 231 | :param user: Zabbix user 232 | 233 | :type password: str 234 | :param password: Zabbix user password 235 | :return: str 236 | """ 237 | base64string = base64.b64encode('{}:{}'.format(user, password).encode()) 238 | return base64string.decode() 239 | 240 | def api_version(self): 241 | """Return version of server Zabbix API. 242 | 243 | :rtype: str 244 | :return: Version of server Zabbix API. 245 | """ 246 | 247 | return self.apiinfo.version() 248 | 249 | def do_request(self, method, params=None): 250 | """Make request to Zabbix API. 251 | 252 | :type method: str 253 | :param method: ZabbixAPI method, like: `apiinfo.version`. 254 | 255 | :type params: str 256 | :param params: ZabbixAPI method arguments. 257 | 258 | >>> from pyzabbix import ZabbixAPI 259 | >>> z = ZabbixAPI() 260 | >>> apiinfo = z.do_request('apiinfo.version') 261 | """ 262 | 263 | request_json = { 264 | 'jsonrpc': '2.0', 265 | 'method': method, 266 | 'params': params or {}, 267 | 'id': '1', 268 | } 269 | 270 | # apiinfo.version and user.login doesn't require auth token 271 | if self.auth and (method not in ('apiinfo.version', 'user.login')): 272 | request_json['auth'] = self.auth 273 | 274 | logger.debug( 275 | 'urllib2.Request({0}, {1})'.format( 276 | self.url, 277 | json.dumps(request_json))) 278 | 279 | data = json.dumps(request_json) 280 | if not isinstance(data, bytes): 281 | data = data.encode("utf-8") 282 | 283 | req = urllib2.Request(self.url, data) 284 | req.get_method = lambda: 'POST' 285 | req.add_header('Content-Type', 'application/json-rpc') 286 | req.add_header('User-Agent', 'py-zabbix/{}'.format(__version__)) 287 | 288 | if self.use_basic_auth: 289 | req.add_header("Authorization", "Basic {}".format(self.base64_cred)) 290 | 291 | try: 292 | res = urlopen(req) 293 | res_str = res.read().decode('utf-8') 294 | res_json = json.loads(res_str) 295 | except ValueError as e: 296 | raise ZabbixAPIException("Unable to parse json: %s" % e.message) 297 | 298 | res_str = json.dumps(res_json, indent=4, separators=(',', ': ')) 299 | logger.debug("Response Body: %s", res_str) 300 | 301 | if 'error' in res_json: 302 | err = res_json['error'].copy() 303 | err.update({'json': str(request_json)}) 304 | raise ZabbixAPIException(err) 305 | 306 | return res_json 307 | 308 | def get_id(self, item_type, item=None, with_id=False, hostid=None, **args): 309 | """Return id or ids of zabbix objects. 310 | 311 | :type item_type: str 312 | :param item_type: Type of zabbix object. (eg host, item etc.) 313 | 314 | :type item: str 315 | :param item: Name of zabbix object. If it is `None`, return list of 316 | all objects in the scope. 317 | 318 | :type with_id: bool 319 | :param with_id: Returned values will be in zabbix json `id` format. 320 | Examlpe: `{'itemid: 128}` 321 | 322 | :type name: bool 323 | :param name: Return name instead of id. 324 | 325 | :type hostid: int 326 | :param hostid: Filter objects by specific hostid. 327 | 328 | :type templateids: int 329 | :param tempateids: Filter objects which only belong to specific 330 | templates by template id. 331 | 332 | :type app_name: str 333 | :param app_name: Filter object which only belong to specific 334 | application. 335 | 336 | :rtype: int or list 337 | :return: Return single `id`, `name` or list of values. 338 | """ 339 | 340 | result = None 341 | name = args.get('name', False) 342 | 343 | type_ = '{item_type}.get'.format(item_type=item_type) 344 | 345 | item_filter_name = { 346 | 'mediatype': 'description', 347 | 'trigger': 'description', 348 | 'triggerprototype': 'description', 349 | 'user': 'alias', 350 | 'usermacro': 'macro', 351 | } 352 | 353 | item_id_name = { 354 | 'discoveryrule': 'item', 355 | 'graphprototype': 'graph', 356 | 'hostgroup': 'group', 357 | 'itemprototype': 'item', 358 | 'map': 'selement', 359 | 'triggerprototype': 'trigger', 360 | 'usergroup': 'usrgrp', 361 | 'usermacro': 'hostmacro', 362 | } 363 | 364 | filter_ = { 365 | 'filter': { 366 | item_filter_name.get(item_type, 'name'): item, 367 | }, 368 | 'output': 'extend'} 369 | 370 | if hostid: 371 | filter_['filter'].update({'hostid': hostid}) 372 | 373 | if args.get('templateids'): 374 | if item_type == 'usermacro': 375 | filter_['hostids'] = args['templateids'] 376 | else: 377 | filter_['templateids'] = args['templateids'] 378 | 379 | if args.get('app_name'): 380 | filter_['application'] = args['app_name'] 381 | 382 | logger.debug( 383 | 'do_request( "{type}", {filter} )'.format( 384 | type=type_, 385 | filter=filter_)) 386 | response = self.do_request(type_, filter_)['result'] 387 | 388 | if response: 389 | item_id_str = item_id_name.get(item_type, item_type) 390 | item_id = '{item}id'.format(item=item_id_str) 391 | result = [] 392 | for obj in response: 393 | # Check if object not belong current template 394 | if args.get('templateids'): 395 | if (not obj.get('templateid') in ("0", None) or 396 | not len(obj.get('templateids', [])) == 0): 397 | continue 398 | 399 | if name: 400 | o = obj.get(item_filter_name.get(item_type, 'name')) 401 | result.append(o) 402 | elif with_id: 403 | result.append({item_id: int(obj.get(item_id))}) 404 | else: 405 | result.append(int(obj.get(item_id))) 406 | 407 | list_types = (list, type(None)) 408 | if not isinstance(item, list_types): 409 | result = result[0] 410 | 411 | return result 412 | -------------------------------------------------------------------------------- /pyzabbix/logger.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | # 3 | # Copyright © 2014 Alexey Dubkov 4 | # 5 | # This file is part of py-zabbix. 6 | # 7 | # Py-zabbix is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Py-zabbix is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with py-zabbix. If not, see . 19 | import logging 20 | import re 21 | 22 | 23 | class NullHandler(logging.Handler): 24 | """Null logger handler. 25 | 26 | :class:`NullHandler` will be used if there are no other logger handlers. 27 | """ 28 | 29 | def emit(self, record): 30 | pass 31 | 32 | 33 | class HideSensitiveFilter(logging.Filter): 34 | """Filter to hide sensitive Zabbix info (password, auth) in logs""" 35 | 36 | def __init__(self, *args, **kwargs): 37 | super(logging.Filter, self).__init__(*args, **kwargs) 38 | self.hide_sensitive = HideSensitiveService.hide_sensitive 39 | 40 | def filter(self, record): 41 | 42 | record.msg = self.hide_sensitive(record.msg) 43 | if record.args: 44 | newargs = [self.hide_sensitive(arg) if isinstance(arg, str) 45 | else arg for arg in record.args] 46 | record.args = tuple(newargs) 47 | 48 | return 1 49 | 50 | 51 | class HideSensitiveService(object): 52 | """ 53 | Service to hide sensitive Zabbix info (password, auth tokens) 54 | Call classmethod hide_sensitive(message: str) 55 | """ 56 | 57 | HIDEMASK = "********" 58 | _pattern = re.compile( 59 | r'(?Ppassword)["\']\s*:\s*u?["\'](?P.+?)["\']' 60 | r'|' 61 | r'\W(?P[a-z0-9]{32})') 62 | 63 | @classmethod 64 | def hide_sensitive(cls, message): 65 | def hide(m): 66 | if m.group('key') == 'password': 67 | return m.string[m.start():m.end()].replace( 68 | m.group('password'), cls.HIDEMASK) 69 | else: 70 | return m.string[m.start():m.end()].replace( 71 | m.group('token'), cls.HIDEMASK) 72 | 73 | message = re.sub(cls._pattern, hide, message) 74 | 75 | return message 76 | -------------------------------------------------------------------------------- /pyzabbix/sender.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | # 3 | # Copyright © 2014 Alexey Dubkov 4 | # 5 | # This file is part of py-zabbix. 6 | # 7 | # Py-zabbix is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Py-zabbix is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with py-zabbix. If not, see . 19 | 20 | from decimal import Decimal 21 | import inspect 22 | import json 23 | import logging 24 | import socket 25 | import struct 26 | import re 27 | 28 | # For python 2 and 3 compatibility 29 | try: 30 | from StringIO import StringIO 31 | import ConfigParser as configparser 32 | except ImportError: 33 | from io import StringIO 34 | import configparser 35 | 36 | from .logger import NullHandler 37 | 38 | null_handler = NullHandler() 39 | logger = logging.getLogger(__name__) 40 | logger.addHandler(null_handler) 41 | 42 | 43 | class ZabbixResponse(object): 44 | """The :class:`ZabbixResponse` contains the parsed response from Zabbix. 45 | """ 46 | def __init__(self): 47 | self._processed = 0 48 | self._failed = 0 49 | self._total = 0 50 | self._time = 0 51 | self._chunk = 0 52 | pattern = (r'[Pp]rocessed:? (\d*);? [Ff]ailed:? (\d*);? ' 53 | r'[Tt]otal:? (\d*);? [Ss]econds spent:? (\d*\.\d*)') 54 | self._regex = re.compile(pattern) 55 | 56 | def __repr__(self): 57 | """Represent detailed ZabbixResponse view.""" 58 | result = json.dumps({'processed': self._processed, 59 | 'failed': self._failed, 60 | 'total': self._total, 61 | 'time': str(self._time), 62 | 'chunk': self._chunk}) 63 | return result 64 | 65 | def parse(self, response): 66 | """Parse zabbix response.""" 67 | info = response.get('info') 68 | res = self._regex.search(info) 69 | 70 | self._processed += int(res.group(1)) 71 | self._failed += int(res.group(2)) 72 | self._total += int(res.group(3)) 73 | self._time += Decimal(res.group(4)) 74 | self._chunk += 1 75 | 76 | @property 77 | def processed(self): 78 | return self._processed 79 | 80 | @property 81 | def failed(self): 82 | return self._failed 83 | 84 | @property 85 | def total(self): 86 | return self._total 87 | 88 | @property 89 | def time(self): 90 | return self._time 91 | 92 | @property 93 | def chunk(self): 94 | return self._chunk 95 | 96 | 97 | class ZabbixMetric(object): 98 | """The :class:`ZabbixMetric` contain one metric for zabbix server. 99 | 100 | :type host: str 101 | :param host: Hostname as it displayed in Zabbix. 102 | 103 | :type key: str 104 | :param key: Key by which you will identify this metric. 105 | 106 | :type value: str 107 | :param value: Metric value. 108 | 109 | :type clock: int 110 | :param clock: Unix timestamp. Current time will used if not specified. 111 | 112 | >>> from pyzabbix import ZabbixMetric 113 | >>> ZabbixMetric('localhost', 'cpu[usage]', 20) 114 | """ 115 | 116 | def __init__(self, host, key, value, clock=None): 117 | self.host = str(host) 118 | self.key = str(key) 119 | self.value = str(value) 120 | if clock: 121 | if isinstance(clock, (float, int)): 122 | self.clock = int(clock) 123 | else: 124 | raise ValueError('Clock must be time in unixtime format') 125 | 126 | def __repr__(self): 127 | """Represent detailed ZabbixMetric view.""" 128 | 129 | result = json.dumps(self.__dict__, ensure_ascii=False) 130 | logger.debug('%s: %s', self.__class__.__name__, result) 131 | 132 | return result 133 | 134 | 135 | class ZabbixSender(object): 136 | """The :class:`ZabbixSender` send metrics to Zabbix server. 137 | 138 | Implementation of 139 | `zabbix protocol `_. 140 | 141 | :type zabbix_server: str 142 | :param zabbix_server: Zabbix server ip address. Default: `127.0.0.1` 143 | 144 | :type zabbix_port: int 145 | :param zabbix_port: Zabbix server port. Default: `10051` 146 | 147 | :type use_config: str 148 | :param use_config: Path to zabbix_agentd.conf file to load settings from. 149 | If value is `True` then default config path will used: 150 | /etc/zabbix/zabbix_agentd.conf 151 | 152 | :type chunk_size: int 153 | :param chunk_size: Number of metrics send to the server at one time 154 | 155 | :type socket_wrapper: function 156 | :param socket_wrapper: to provide a socket wrapper function to be used to 157 | wrap the socket connection to zabbix. 158 | Example: 159 | from pyzabbix import ZabbixSender 160 | import ssl 161 | secure_connection_option = dict(..) 162 | zs = ZabbixSender( 163 | zabbix_server=zabbix_server, 164 | zabbix_port=zabbix_port, 165 | socket_wrapper=lambda sock:ssl.wrap_socket(sock,**secure_connection_option) 166 | ) 167 | 168 | :type timeout: int 169 | :param timeout: Number of seconds before call to Zabbix server times out 170 | Default: 10 171 | >>> from pyzabbix import ZabbixMetric, ZabbixSender 172 | >>> metrics = [] 173 | >>> m = ZabbixMetric('localhost', 'cpu[usage]', 20) 174 | >>> metrics.append(m) 175 | >>> zbx = ZabbixSender('127.0.0.1') 176 | >>> zbx.send(metrics) 177 | """ 178 | 179 | def __init__(self, 180 | zabbix_server='127.0.0.1', 181 | zabbix_port=10051, 182 | use_config=None, 183 | chunk_size=250, 184 | socket_wrapper=None, 185 | timeout=10): 186 | 187 | self.chunk_size = chunk_size 188 | self.timeout = timeout 189 | 190 | self.socket_wrapper = socket_wrapper 191 | if use_config: 192 | self.zabbix_uri = self._load_from_config(use_config) 193 | else: 194 | self.zabbix_uri = [(zabbix_server, zabbix_port)] 195 | 196 | def __repr__(self): 197 | """Represent detailed ZabbixSender view.""" 198 | 199 | result = json.dumps(self.__dict__, ensure_ascii=False) 200 | logger.debug('%s: %s', self.__class__.__name__, result) 201 | 202 | return result 203 | 204 | def _load_from_config(self, config_file): 205 | """Load zabbix server IP address and port from zabbix agent config 206 | file. 207 | 208 | If ServerActive variable is not found in the file, it will 209 | use the default: 127.0.0.1:10051 210 | 211 | :type config_file: str 212 | :param use_config: Path to zabbix_agentd.conf file to load settings 213 | from. If value is `True` then default config path will used: 214 | /etc/zabbix/zabbix_agentd.conf 215 | """ 216 | 217 | if config_file and isinstance(config_file, bool): 218 | config_file = '/etc/zabbix/zabbix_agentd.conf' 219 | 220 | logger.debug("Used config: %s", config_file) 221 | 222 | # This is workaround for config wile without sections 223 | with open(config_file, 'r') as f: 224 | config_file_data = "[root]\n" + f.read() 225 | 226 | params = {} 227 | 228 | try: 229 | # python2 230 | args = inspect.getargspec( 231 | configparser.RawConfigParser.__init__).args 232 | except ValueError: 233 | # python3 234 | args = inspect.getfullargspec( 235 | configparser.RawConfigParser.__init__).kwonlyargs 236 | 237 | if 'strict' in args: 238 | params['strict'] = False 239 | 240 | config_file_fp = StringIO(config_file_data) 241 | config = configparser.RawConfigParser(**params) 242 | config.readfp(config_file_fp) 243 | # Prefer ServerActive, then try Server and fallback to defaults 244 | if config.has_option('root', 'ServerActive'): 245 | zabbix_serveractives = config.get('root', 'ServerActive') 246 | elif config.has_option('root', 'Server'): 247 | zabbix_serveractives = config.get('root', 'Server') 248 | else: 249 | zabbix_serveractives = '127.0.0.1:10051' 250 | 251 | result = [] 252 | for serverport in zabbix_serveractives.split(','): 253 | if ':' not in serverport: 254 | serverport = "%s:%s" % (serverport.strip(), 10051) 255 | server, port = serverport.split(':') 256 | serverport = (server, int(port)) 257 | result.append(serverport) 258 | logger.debug("Loaded params: %s", result) 259 | 260 | return result 261 | 262 | def _receive(self, sock, count): 263 | """Reads socket to receive data from zabbix server. 264 | 265 | :type socket: :class:`socket._socketobject` 266 | :param socket: Socket to read. 267 | 268 | :type count: int 269 | :param count: Number of bytes to read from socket. 270 | """ 271 | 272 | buf = b'' 273 | 274 | while len(buf) < count: 275 | chunk = sock.recv(count - len(buf)) 276 | if not chunk: 277 | break 278 | buf += chunk 279 | 280 | return buf 281 | 282 | def _create_messages(self, metrics): 283 | """Create a list of zabbix messages from a list of ZabbixMetrics. 284 | 285 | :type metrics_array: list 286 | :param metrics_array: List of :class:`zabbix.sender.ZabbixMetric`. 287 | 288 | :rtype: list 289 | :return: List of zabbix messages. 290 | """ 291 | 292 | messages = [] 293 | 294 | # Fill the list of messages 295 | for m in metrics: 296 | messages.append(str(m)) 297 | 298 | logger.debug('Messages: %s', messages) 299 | 300 | return messages 301 | 302 | def _create_request(self, messages): 303 | """Create a formatted request to zabbix from a list of messages. 304 | 305 | :type messages: list 306 | :param messages: List of zabbix messages 307 | 308 | :rtype: list 309 | :return: Formatted zabbix request 310 | """ 311 | 312 | msg = ','.join(messages) 313 | request = '{{"request":"sender data","data":[{msg}]}}'.format(msg=msg) 314 | request = request.encode("utf-8") 315 | logger.debug('Request: %s', request) 316 | 317 | return request 318 | 319 | def _create_packet(self, request): 320 | """Create a formatted packet from a request. 321 | 322 | :type request: str 323 | :param request: Formatted zabbix request 324 | 325 | :rtype: str 326 | :return: Data packet for zabbix 327 | """ 328 | 329 | data_len = struct.pack(', 276 | # See 'zabbix_agentd' directory for examples. 277 | # 278 | # Mandatory: no 279 | # Default: 280 | # UserParameter= 281 | 282 | ####### LOADABLE MODULES ####### 283 | 284 | ### Option: LoadModulePath 285 | # Full path to location of agent modules. 286 | # Default depends on compilation options. 287 | # 288 | # Mandatory: no 289 | # Default: 290 | # LoadModulePath=${libdir}/modules 291 | 292 | ### Option: LoadModule 293 | # Module to load at agent startup. Modules are used to extend functionality of the agent. 294 | # Format: LoadModule= 295 | # The modules must be located in directory specified by LoadModulePath. 296 | # It is allowed to include multiple LoadModule parameters. 297 | # 298 | # Mandatory: no 299 | # Default: 300 | # LoadModule= 301 | -------------------------------------------------------------------------------- /tests/test_Functional_API.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from unittest import TestCase, skipIf 4 | from pyzabbix import ZabbixAPI, ZabbixAPIException 5 | 6 | 7 | @skipIf('TRAVIS' not in os.environ.keys(), "Travis CI test") 8 | class FunctionalAPI(TestCase): 9 | def test_LoginToServer(self): 10 | try: 11 | ZabbixAPI(url='http://127.0.0.1', 12 | user='Admin', 13 | password='zabbix') 14 | except ZabbixAPIException: 15 | self.fail('Can\'t login to Zabbix') 16 | 17 | def test_LoginToServerSSL(self): 18 | try: 19 | ZabbixAPI(url='https://127.0.0.1', 20 | user='Admin', 21 | password='zabbix') 22 | except ZabbixAPIException: 23 | self.fail('Can\'t login to Zabbix') 24 | 25 | def test_get_api_version(self): 26 | zapi = ZabbixAPI(url='http://127.0.0.1', 27 | user='Admin', 28 | password='zabbix') 29 | if os.environ['ZABBIX_VERSION'] == '3': 30 | self.assertEqual(zapi.api_version(), '3.0.1') 31 | elif os.environ['ZABBIX_VERSION'] == '2': 32 | self.assertEqual(zapi.api_version(), '2.4.7') 33 | else: 34 | self.fail('api_version() not tested!') 35 | -------------------------------------------------------------------------------- /tests/test_Functional_API_Old.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from unittest import TestCase, skipIf 4 | from zabbix.api import ZabbixAPI 5 | from pyzabbix import ZabbixAPIException 6 | 7 | 8 | @skipIf('TRAVIS' not in os.environ.keys(), "Travis CI test") 9 | class FunctionalAPI(TestCase): 10 | def test_LoginToServer(self): 11 | try: 12 | ZabbixAPI(url='http://127.0.0.1', 13 | user='Admin', 14 | password='zabbix') 15 | except ZabbixAPIException: 16 | self.fail('Can\'t login to Zabbix') 17 | -------------------------------------------------------------------------------- /tests/test_Functional_Sender.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from unittest import TestCase, skipIf 4 | from time import time as now 5 | 6 | from pyzabbix import ZabbixMetric, ZabbixSender, ZabbixResponse 7 | 8 | 9 | @skipIf('TRAVIS' not in os.environ.keys(), "Travis CI test") 10 | class FunctionalSender(TestCase): 11 | def test_sendMetricsToServer(self): 12 | cur_date_unix = int(now()) 13 | m = [ 14 | ZabbixMetric('host2', 'key3', 'IDDQD'), 15 | ZabbixMetric('host1', 'key1', 33.1, cur_date_unix) 16 | ] 17 | 18 | z = ZabbixSender('127.0.0.1', 10051, chunk_size=1).send(m) 19 | 20 | self.assertIsInstance(z, ZabbixResponse) 21 | self.assertEqual(z.total, 2) 22 | self.assertEqual(z.processed, 2) 23 | self.assertEqual(z.failed, 0) 24 | self.assertEqual(z.chunk, 2) 25 | -------------------------------------------------------------------------------- /tests/test_Functional_Sender_Old.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from unittest import TestCase, skipIf 4 | from time import time as now 5 | 6 | from zabbix.sender import ZabbixMetric, ZabbixSender 7 | from pyzabbix import ZabbixResponse 8 | 9 | 10 | @skipIf('TRAVIS' not in os.environ.keys(), "Travis CI test") 11 | class FunctionalSender(TestCase): 12 | def test_FunctionalSenderOld(self): 13 | cur_date_unix = int(now()) 14 | m = [ 15 | ZabbixMetric('host2', 'key3', 'IDDQD'), 16 | ZabbixMetric('host1', 'key1', 33.1, cur_date_unix) 17 | ] 18 | 19 | z = ZabbixSender('127.0.0.1', 10051, chunk_size=1).send(m) 20 | 21 | self.assertIsInstance(z, ZabbixResponse) 22 | self.assertEqual(z.total, 2) 23 | self.assertEqual(z.processed, 2) 24 | self.assertEqual(z.failed, 0) 25 | self.assertEqual(z.chunk, 2) 26 | -------------------------------------------------------------------------------- /tests/test_api.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import unittest 4 | from pyzabbix import ZabbixAPI, ZabbixAPIException, ssl_context_compat 5 | from pyzabbix.logger import HideSensitiveService 6 | try: 7 | from mock import patch 8 | except ImportError: 9 | from unittest.mock import patch 10 | from sys import version_info 11 | 12 | # For Python 2 and 3 compatibility 13 | if version_info[0] == 2: 14 | urlopen = 'urllib2.urlopen' 15 | res_type = str 16 | elif version_info[0] >= 3: 17 | res_type = bytes 18 | urlopen = 'urllib.request.urlopen' 19 | 20 | 21 | class MockResponse(object): 22 | 23 | def __init__(self, ret, code=200, msg='OK'): 24 | self.ret = ret 25 | self.code = code 26 | self.msg = msg 27 | self.headers = {'content-type': 'text/plain; charset=utf-8'} 28 | 29 | def read(self): 30 | return res_type(self.ret.encode('utf-8')) 31 | 32 | def getcode(self): 33 | return self.code 34 | 35 | 36 | class TestZabbixAPI(unittest.TestCase): 37 | 38 | def setUp(self): 39 | "Mock urllib2.urlopen" 40 | self.patcher = patch(urlopen) 41 | self.urlopen_mock = self.patcher.start() 42 | 43 | def test_decorator_ssl_context_compat(self): 44 | @ssl_context_compat 45 | def test_decorator(*args, **kwargs): 46 | def response(*args, **kwargs): 47 | return args, kwargs 48 | return response(*args, **kwargs) 49 | 50 | arg, context = test_decorator(True) 51 | self.assertIs(arg[0], True) 52 | self.assertIn('context', context, msg='SSL context is missing.') 53 | self.assertIsNotNone(context.get('context'), 54 | msg='SSL context is None.') 55 | 56 | def test_api_version(self): 57 | ret = {'result': '2.2.5'} 58 | self.urlopen_mock.return_value = MockResponse(json.dumps(ret)) 59 | res = ZabbixAPI().api_version() 60 | self.assertEqual(res, '2.2.5') 61 | 62 | def test_login(self): 63 | req = {'user': 'Admin', 'password': 'zabbix'} 64 | ret = { 65 | 'jsonrpc': '2.0', 66 | 'result': '0424bd59b807674191e7d77572075f33', 67 | 'id': 1 68 | } 69 | self.urlopen_mock.return_value = MockResponse(json.dumps(ret)) 70 | res = ZabbixAPI().user.login(**req) 71 | self.assertEqual(res, '0424bd59b807674191e7d77572075f33') 72 | 73 | def test_login_with(self): 74 | """Test automatic user.logout when using context manager""" 75 | 76 | login_ret = { 77 | 'jsonrpc': '2.0', 78 | 'result': '0424bd59b807674191e7d77572075f33', 79 | 'id': 1 80 | } 81 | logout_ret = { 82 | "jsonrpc": "2.0", 83 | "result": True, 84 | "id": 1 85 | } 86 | self.urlopen_mock.side_effect = [MockResponse(json.dumps(login_ret)), 87 | MockResponse(json.dumps(logout_ret))] 88 | 89 | with ZabbixAPI() as zapi: 90 | # Check you are authenticated: 91 | self.assertEqual(zapi.auth, login_ret['result']) 92 | # Check that you are no longer authenticated when outside: 93 | self.assertEqual(zapi.auth, None) 94 | # Zabbix API is accessed two times: user.login(), user.logout(). 95 | self.assertEqual(self.urlopen_mock.call_count, 2) 96 | 97 | def test_do_request(self): 98 | req = 'apiinfo.version' 99 | ret = { 100 | 'jsonrpc': '2.0', 101 | 'result': '2.2.5', 102 | 'id': 1 103 | } 104 | self.urlopen_mock.return_value = MockResponse(json.dumps(ret)) 105 | res = ZabbixAPI().do_request(req) 106 | self.assertEqual(res, ret) 107 | 108 | def test_get_id_item(self): 109 | ret = { 110 | 'jsonrpc': '2.0', 111 | 'result': 112 | [{ 113 | 'itemid': '23298', 114 | 'hostid': '10084', 115 | 'name': 'Test Item', 116 | 'key_': 'system.cpu.switches', 117 | 'description': '', 118 | }], 119 | 'id': 1, 120 | } 121 | self.urlopen_mock.return_value = MockResponse(json.dumps(ret)) 122 | res = ZabbixAPI().get_id('item', item='Test Item') 123 | self.assertEqual(res, 23298) 124 | 125 | @unittest.skipUnless(version_info >= (3, 4), 126 | "Test not supported for python < 3.4") 127 | def test_hide_sensitive_in_logger(self): 128 | """Test that logger hides passwords and auth keys (python 3.4+)""" 129 | 130 | ret = { 131 | 'jsonrpc': '2.0', 132 | 'result': '0424bd59b807674191e7d77572075f33', 133 | 'id': 1 134 | } 135 | self.urlopen_mock.return_value = MockResponse(json.dumps(ret)) 136 | 137 | with self.assertLogs('pyzabbix', level='DEBUG') as cm: 138 | 139 | # Create ZabbixAPI class instance 140 | zapi = ZabbixAPI(url='https://localhost/zabbix', 141 | user='Admin', password='PASSWORD') 142 | 143 | ret = { 144 | 'jsonrpc': '2.0', 145 | 'result': 146 | [{ 147 | 'itemid': '23298', 148 | 'hostid': '10084', 149 | 'name': 'Test Item', 150 | 'key_': 'system.cpu.switches', 151 | 'description': '', 152 | }], 153 | 'id': 1, 154 | } 155 | self.urlopen_mock.return_value = MockResponse(json.dumps(ret)) 156 | zapi.get_id('item', item='Test Item') 157 | 158 | log_string = "".join(cm.output) 159 | 160 | self.assertNotIn('PASSWORD', log_string) 161 | self.assertNotIn('0424bd59b807674191e7d77572075f33', log_string) 162 | 163 | # count number or passwords/token replacements 164 | # (including 'DEBUG:pyzabbix.api:ZabbixAPI.login(Admin,********)') 165 | self.assertEqual(log_string.count(HideSensitiveService.HIDEMASK), 4) 166 | 167 | def test_hide_sensitive_in_exception(self): 168 | """Test that exception raised hides passwords and auth keys""" 169 | 170 | with self.assertRaises(ZabbixAPIException) as cm: 171 | res = { 172 | 'code': -32602, 173 | 'message': 'Invalid params', 174 | 'data': 'Incorrect API "host2".', 175 | 'json': """ 176 | {'jsonrpc': '2.0', 177 | 'method': 'host2.get', 178 | 'params': {'monitored_hosts': 1, 'output': 'extend'}, 179 | 'id': '1', 180 | 'auth': '0424bd59b807674191e7d77572075f33'} 181 | """ 182 | } 183 | raise ZabbixAPIException(res) 184 | 185 | self.assertNotIn("0424bd59b807674191e7d77572075f33", cm.exception.json) 186 | self.assertEqual( 187 | cm.exception.json.count(HideSensitiveService.HIDEMASK), 188 | 1) 189 | 190 | def tearDown(self): 191 | self.patcher.stop() 192 | -------------------------------------------------------------------------------- /tests/test_hide_sensitive_filter.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pyzabbix.logger import HideSensitiveService 3 | 4 | 5 | class TestHideSensitiveFilter(unittest.TestCase): 6 | 7 | def test_hide_filter_multi(self): 8 | 9 | test_message = '"password": "hide_this", ' \ 10 | '"result": "0424bd59b807674191e7d77572075f33", ' \ 11 | '"result": "do_not_hide_this", ' \ 12 | '"auth": "0424bd59b807674191e7d77572075f33"' 13 | expected = ('"password": "{}", ' 14 | '"result": "{}", ' 15 | '"result": "do_not_hide_this", ' 16 | '"auth": "{}"').format( 17 | HideSensitiveService.HIDEMASK, 18 | HideSensitiveService.HIDEMASK, 19 | HideSensitiveService.HIDEMASK) 20 | 21 | self.assertEqual(HideSensitiveService.hide_sensitive(test_message), 22 | expected) 23 | 24 | def test_hide_filter_do_not_change_url(self): 25 | 26 | # Filter should not hide 'zabbix' in URL: 27 | # https://localhost/zabbix/api_jsonrpc.php 28 | test = 'urllib2.Request(https://localhost/zabbix/api_jsonrpc.php,' \ 29 | '{ ... "params": {"user": "Admin", "password": "zabbix"}, '\ 30 | '"id": "1"}))' 31 | expected = 'urllib2.Request(https://localhost/zabbix/api_jsonrpc.php,'\ 32 | '{ ... "params": {"user": "Admin", "password": "' + \ 33 | HideSensitiveService.HIDEMASK + '"}, "id": "1"}))' 34 | 35 | self.assertEqual(HideSensitiveService.hide_sensitive(test), 36 | expected) 37 | -------------------------------------------------------------------------------- /tests/test_sender.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import socket 4 | 5 | import struct 6 | 7 | from unittest import TestCase, skip 8 | # Python 2 and 3 compatibility 9 | try: 10 | from mock import patch, call, mock_open 11 | autospec = None 12 | except ImportError: 13 | from unittest.mock import patch, call, mock_open 14 | autospec = True 15 | 16 | from pyzabbix import ZabbixMetric, ZabbixSender, ZabbixResponse 17 | 18 | 19 | class TestZabbixResponse(TestCase): 20 | def test_init(self): 21 | zr = ZabbixResponse() 22 | self.assertEqual(zr.failed, 0) 23 | self.assertEqual(zr.processed, 0) 24 | self.assertEqual(zr.time, 0) 25 | self.assertEqual(zr.total, 0) 26 | self.assertEqual(zr.chunk, 0) 27 | 28 | def test_repr(self): 29 | zr = ZabbixResponse() 30 | result = json.dumps({'processed': zr._processed, 31 | 'failed': zr._failed, 32 | 'total': zr._total, 33 | 'time': str(zr._time), 34 | 'chunk': zr._chunk}) 35 | self.assertEqual(zr.__repr__(), result) 36 | 37 | 38 | class TestZabbixMetric(TestCase): 39 | def test_init(self): 40 | zm = ZabbixMetric('host1', 'key1', 100500, 1457358608) 41 | self.assertEqual(zm.host, 'host1') 42 | self.assertEqual(zm.key, 'key1') 43 | self.assertEqual(zm.clock, 1457358608) 44 | 45 | def test_init_err(self): 46 | with self.assertRaises(ValueError): 47 | ZabbixMetric('host1', 'key1', 100500, '1457358608.01') 48 | 49 | def test_repr(self): 50 | zm = ZabbixMetric('host1', 'key1', 100500) 51 | zm_repr = json.loads(zm.__repr__()) 52 | self.assertEqual(zm_repr, zm.__dict__) 53 | 54 | 55 | class TestsZabbixSender(TestCase): 56 | def setUp(self): 57 | self.resp_header = b'ZBXD\x01\\\x00\x00\x00\x00\x00\x00\x00' 58 | self.resp_body = b'''{"response":"success","info":"processed: 0; \ 59 | failed: 10; total: 10; seconds spent: 0.000078"} 60 | ''' 61 | 62 | def test_init(self): 63 | zs = ZabbixSender() 64 | self.assertEqual(zs.__class__.__name__, 'ZabbixSender') 65 | self.assertEqual(isinstance(zs.zabbix_uri[0], tuple), True) 66 | self.assertEqual(zs.zabbix_uri[0][0], '127.0.0.1') 67 | self.assertEqual(zs.zabbix_uri[0][1], 10051) 68 | 69 | def test_init_config(self): 70 | folder = os.path.dirname(__file__) 71 | filename = os.path.join(folder, 'data/zabbix_agentd.conf') 72 | zs = ZabbixSender(use_config=filename) 73 | self.assertEqual(zs.__class__.__name__, 'ZabbixSender') 74 | self.assertEqual(isinstance(zs.zabbix_uri[0], tuple), True) 75 | self.assertEqual(zs.zabbix_uri[0][0], '192.168.1.2') 76 | self.assertEqual(zs.zabbix_uri[0][1], 10051) 77 | 78 | def test_init_config_exception(self): 79 | folder = os.path.dirname(__file__) 80 | filename = os.path.join(folder, 'zabbix_agent.conf') 81 | with self.assertRaises(Exception): 82 | ZabbixSender(use_config=filename) 83 | 84 | def test_init_config_default(self): 85 | folder = os.path.dirname(__file__) 86 | filename = os.path.join(folder, 'data/zabbix_agentd.conf') 87 | file = open(filename, 'r') 88 | f = file.read() 89 | with patch('pyzabbix.sender.open', mock_open(read_data=f)): 90 | zs = ZabbixSender(use_config=True) 91 | self.assertEqual(zs.zabbix_uri, [('192.168.1.2', 10051)]) 92 | file.close() 93 | 94 | def test_repr(self): 95 | zs = ZabbixSender() 96 | self.assertEqual(zs.__repr__(), json.dumps(zs.__dict__)) 97 | 98 | def test_load_from_config(self): 99 | folder = os.path.dirname(__file__) 100 | filename = os.path.join(folder, 'data/zabbix_agentd.conf') 101 | zs = ZabbixSender() 102 | result = zs._load_from_config(config_file=filename) 103 | self.assertEqual(result, [('192.168.1.2', 10051)]) 104 | 105 | def test_create_messages(self): 106 | m = [ZabbixMetric('host1', 'key1', 1), 107 | ZabbixMetric('host2', 'key2', 2)] 108 | zs = ZabbixSender() 109 | result = zs._create_messages(m) 110 | self.assertIsInstance(result, list) 111 | self.assertEqual(len(result), 2) 112 | 113 | def test_create_request(self): 114 | message = [ 115 | '{"clock": "1457445366", "host": "host1",\ 116 | "value": "1", "key": "key1"}', 117 | '{"clock": "1457445366", "host": "host2",\ 118 | "value": "2", "key": "key2"}'] 119 | zs = ZabbixSender() 120 | result = zs._create_request(message) 121 | self.assertIsInstance(result, bytes) 122 | result = json.loads(result.decode()) 123 | self.assertEqual(result['request'], 'sender data') 124 | self.assertEqual(len(result['data']), 2) 125 | 126 | def test_create_request_failed(self): 127 | message = [ 128 | '{"clock": "1457445366", "host: \ 129 | "host1", "value": "1", "key": "key1"}', 130 | '{"clock": "1457445366", "host": \ 131 | "host2", "value": "2", "key": "key2"}'] 132 | zs = ZabbixSender() 133 | result = zs._create_request(message) 134 | with self.assertRaises(Exception): 135 | result = json.loads(result.decode()) 136 | 137 | def test_create_packet(self): 138 | message = [ 139 | '{"clock": "1457445366", "host": "host1",\ 140 | "value": "1", "key": "key1"}', 141 | '{"clock": "1457445366", "host": "host2",\ 142 | "value": "2", "key": "key2"}'] 143 | zs = ZabbixSender() 144 | request = zs._create_request(message) 145 | result = zs._create_packet(request) 146 | data_len = struct.pack('