├── .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('