From 5385de9f445edebaca4b869e7e5b7cc94b048554 Mon Sep 17 00:00:00 2001 From: Aleksandr Iantsen Date: Tue, 4 Jun 2024 15:04:05 +0300 Subject: [PATCH] added support for new version of Zabbix --- .github/scripts/compatibility_api_test_7.py | 451 ++++++++++++++++++++ .github/workflows/compatibility_70.yaml | 90 ++++ README.md | 4 +- 3 files changed, 544 insertions(+), 1 deletion(-) create mode 100644 .github/scripts/compatibility_api_test_7.py create mode 100644 .github/workflows/compatibility_70.yaml diff --git a/.github/scripts/compatibility_api_test_7.py b/.github/scripts/compatibility_api_test_7.py new file mode 100644 index 0000000..ba85b4e --- /dev/null +++ b/.github/scripts/compatibility_api_test_7.py @@ -0,0 +1,451 @@ +#!/usr/bin/env python +# Copyright (C) 2001-2023 Zabbix SIA +# +# Zabbix SIA licenses this file under the MIT License. +# See the LICENSE file in the project root for more information. + +import sys +import time +import unittest + +sys.path.append('.') +from zabbix_utils.api import ZabbixAPI +from zabbix_utils.sender import Sender +from zabbix_utils.getter import Getter +from zabbix_utils.aioapi import AsyncZabbixAPI +from zabbix_utils.aiosender import AsyncSender +from zabbix_utils.aiogetter import AsyncGetter +from zabbix_utils.exceptions import APIRequestError +from zabbix_utils.types import AgentResponse, ItemValue, TrapperResponse, APIVersion + +ZABBIX_URL = '127.0.0.1' +ZABBIX_USER = 'Admin' +ZABBIX_PASSWORD = 'zabbix' + + +class CompatibilityAPITest(unittest.TestCase): + """Compatibility synchronous test with Zabbix API version 7.0""" + + def setUp(self): + self.url = ZABBIX_URL + self.user = ZABBIX_USER + self.password = ZABBIX_PASSWORD + self.token_id = None + self.token = None + self.zapi = ZabbixAPI( + url=self.url + ) + self._create_token() + + def _create_token(self): + """Tests auth using username and password""" + + self.assertEqual( + type(self.zapi), ZabbixAPI, "Creating ZabbixAPI object was going wrong") + + self.assertEqual( + type(self.zapi.api_version()), APIVersion, "Version getting was going wrong") + + self.zapi.login( + user=self.user, + password=self.password + ) + + self.assertIsNotNone(self.zapi._ZabbixAPI__session_id, "Login by user and password was going wrong") + + resp = self.zapi.user.checkAuthentication(sessionid=self.zapi._ZabbixAPI__session_id) + + self.assertEqual( + type(resp), dict, "Request user.checkAuthentication was going wrong") + + tokens = self.zapi.token.get( + filter={'name': f"{self.user} [{self.__class__.__name__}]"}, + output=['tokenid'] + ) + + if tokens: + self.token_id = int(tokens[0]['tokenid']) + self.assertEqual( + type(self.token_id), int, "Request token.get was going wrong") + else: + self.token_id = int(self.zapi.token.create( + name=f"{self.user} [{self.__class__.__name__}]" + )['tokenids'][0]) + self.assertEqual( + type(self.token_id), int, "Request token.create was going wrong") + + self.token = self.zapi.token.generate(*[self.token_id])[0]['token'] + self.assertEqual(type(self.token), str, "Request token.generate was going wrong") + + self.zapi.logout() + + self.assertIsNone(self.zapi._ZabbixAPI__session_id, "Logout was going wrong") + + with self.assertRaises(APIRequestError, + msg="Request user.checkAuthentication after logout was going wrong"): + resp = self.zapi.user.checkAuthentication(sessionid=(self.zapi._ZabbixAPI__session_id or '')) + + def test_classic_auth(self): + """Tests auth using username and password""" + + self._create_token() + + def test_token_auth(self): + """Tests auth using token""" + + self.assertEqual( + type(self.zapi), ZabbixAPI, "Creating ZabbixAPI object was going wrong") + + self.assertEqual( + type(self.zapi.api_version()), APIVersion, "Version getting was going wrong") + + self.zapi.login(token=self.token) + + self.assertIsNotNone(self.zapi._ZabbixAPI__session_id, "Login by token was going wrong") + + resp = self.zapi.user.checkAuthentication(token=self.token) + + self.assertEqual( + type(resp), dict, "Request user.checkAuthentication was going wrong") + + +class CompatibilitySenderTest(unittest.TestCase): + """Compatibility synchronous test with Zabbix sender version 7.0""" + + def setUp(self): + self.ip = ZABBIX_URL + self.port = 10051 + self.chunk_size = 10 + self.sender = Sender( + server=self.ip, + port=self.port, + chunk_size=self.chunk_size + ) + self.hostname = f"{self.__class__.__name__}_host" + self.itemname = f"{self.__class__.__name__}_item" + self.itemkey = f"{self.__class__.__name__}" + self.prepare_items() + + def prepare_items(self): + """Creates host and items for sending values later""" + + zapi = ZabbixAPI( + url=ZABBIX_URL, + user=ZABBIX_USER, + password=ZABBIX_PASSWORD, + skip_version_check=True + ) + + hosts = zapi.host.get( + filter={'host': self.hostname}, + output=['hostid'] + ) + + hostid = None + if len(hosts) > 0: + hostid = hosts[0].get('hostid') + + if not hostid: + hostid = zapi.host.create( + host=self.hostname, + interfaces=[{ + "type": 1, + "main": 1, + "useip": 1, + "ip": "127.0.0.1", + "dns": "", + "port": "10050" + }], + groups=[{"groupid": "2"}] + )['hostids'][0] + + self.assertIsNotNone(hostid, "Creating test host was going wrong") + + items = zapi.item.get( + filter={'key_': self.itemkey}, + output=['itemid'] + ) + + itemid = None + if len(items) > 0: + itemid = items[0].get('itemid') + + if not itemid: + itemid = zapi.item.create( + name=self.itemname, + key_=self.itemkey, + hostid=hostid, + type=2, + value_type=3 + )['itemids'][0] + + time.sleep(2) + + self.assertIsNotNone(hostid, "Creating test item was going wrong") + + zapi.logout() + + def test_send_values(self): + """Tests sending item values""" + + items = [ + ItemValue(self.hostname, self.itemkey, 10), + ItemValue(self.hostname, self.itemkey, 'test message'), + ItemValue(self.hostname, 'item_key1', -1, 1695713666), + ItemValue(self.hostname, 'item_key2', '{"msg":"test message"}'), + ItemValue(self.hostname, self.itemkey, 0, 1695713666, 100), + ItemValue(self.hostname, self.itemkey, 5.5, 1695713666) + ] + resp = self.sender.send(items) + self.assertEqual(type(resp), TrapperResponse, "Sending item values was going wrong") + self.assertEqual(resp.total, len(items), "Total number of the sent values is unexpected") + self.assertEqual(resp.processed, 4, "Number of the processed values is unexpected") + self.assertEqual(resp.failed, (resp.total - resp.processed), "Number of the failed values is unexpected") + + first_chunk = list(resp.details.values())[0][0] + self.assertEqual(type(first_chunk), TrapperResponse, "Sending item values was going wrong") + self.assertEqual(first_chunk.total, len(items), "Total number of the sent values is unexpected") + self.assertEqual(first_chunk.processed, 4, "Number of the processed values is unexpected") + self.assertEqual(first_chunk.failed, (first_chunk.total - first_chunk.processed), "Number of the failed values is unexpected") + + +class CompatibilityGetTest(unittest.TestCase): + """Compatibility synchronous test with Zabbix get version 7.0""" + + def setUp(self): + self.host = ZABBIX_URL + self.port = 10050 + self.agent = Getter( + host=self.host, + port=self.port + ) + + def test_get_values(self): + """Tests getting item values""" + + resp = self.agent.get('system.uname') + + self.assertIsNotNone(resp, "Getting item values was going wrong") + self.assertEqual(type(resp), AgentResponse, "Got value is unexpected") + self.assertEqual(type(resp.value), str, "Got value is unexpected") + + +class CompatibilityAsyncAPITest(unittest.IsolatedAsyncioTestCase): + """Compatibility asynchronous test with Zabbix API version 7.0""" + + async def asyncSetUp(self): + self.url = ZABBIX_URL + self.user = ZABBIX_USER + self.password = ZABBIX_PASSWORD + self.token_id = None + self.token = None + self.zapi = AsyncZabbixAPI( + url=self.url + ) + await self._create_token() + + async def asyncTearDown(self): + if self.zapi: + await self.zapi.logout() + + async def _create_token(self): + """Tests auth using username and password""" + + self.assertEqual( + type(self.zapi), AsyncZabbixAPI, "Creating AsyncZabbixAPI object was going wrong") + + self.assertEqual( + type(self.zapi.api_version()), APIVersion, "Version getting was going wrong") + + await self.zapi.login( + user=self.user, + password=self.password + ) + + self.assertIsNotNone(self.zapi._AsyncZabbixAPI__session_id, "Login by user and password was going wrong") + + resp = await self.zapi.user.checkAuthentication(sessionid=self.zapi._AsyncZabbixAPI__session_id) + + self.assertEqual( + type(resp), dict, "Request user.checkAuthentication was going wrong") + + tokens = await self.zapi.token.get( + filter={'name': f"{self.user} [{self.__class__.__name__}]"}, + output=['tokenid'] + ) + + if tokens: + self.token_id = int(tokens[0]['tokenid']) + self.assertEqual( + type(self.token_id), int, "Request token.get was going wrong") + else: + created_token = await self.zapi.token.create( + name=f"{self.user} [{self.__class__.__name__}]" + ) + self.token_id = int(created_token['tokenids'][0]) + self.assertEqual( + type(self.token_id), int, "Request token.create was going wrong") + + generated_token = await self.zapi.token.generate(*[self.token_id]) + self.token = generated_token[0]['token'] + self.assertEqual(type(self.token), str, "Request token.generate was going wrong") + + async def test_classic_auth(self): + """Tests auth using username and password""" + + await self._create_token() + + async def test_token_auth(self): + """Tests auth using token""" + + self.assertEqual( + type(self.zapi), AsyncZabbixAPI, "Creating AsyncZabbixAPI object was going wrong") + + self.assertEqual( + type(self.zapi.api_version()), APIVersion, "Version getting was going wrong") + + await self.zapi.login(token=self.token) + + self.assertIsNotNone(self.zapi._AsyncZabbixAPI__session_id, "Login by token was going wrong") + + resp = await self.zapi.user.checkAuthentication(token=self.token) + + self.assertEqual( + type(resp), dict, "Request user.checkAuthentication was going wrong") + + await self.zapi.logout() + + self.assertIsNone(self.zapi._AsyncZabbixAPI__session_id, "Logout was going wrong") + + with self.assertRaises(RuntimeError, + msg="Request user.checkAuthentication after logout was going wrong"): + resp = await self.zapi.user.checkAuthentication(sessionid=(self.zapi._AsyncZabbixAPI__session_id or '')) + + +class CompatibilityAsyncSenderTest(unittest.IsolatedAsyncioTestCase): + """Compatibility asynchronous test with Zabbix sender version 7.0""" + + async def asyncSetUp(self): + self.ip = ZABBIX_URL + self.port = 10051 + self.chunk_size = 10 + self.sender = AsyncSender( + server=self.ip, + port=self.port, + chunk_size=self.chunk_size + ) + self.hostname = f"{self.__class__.__name__}_host" + self.itemname = f"{self.__class__.__name__}_item" + self.itemkey = f"{self.__class__.__name__}" + await self.prepare_items() + + async def prepare_items(self): + """Creates host and items for sending values later""" + + zapi = AsyncZabbixAPI( + url=ZABBIX_URL, + skip_version_check=True + ) + await zapi.login( + user=ZABBIX_USER, + password=ZABBIX_PASSWORD + ) + + hosts = await zapi.host.get( + filter={'host': self.hostname}, + output=['hostid'] + ) + + hostid = None + if len(hosts) > 0: + hostid = hosts[0].get('hostid') + + if not hostid: + created_host = await zapi.host.create( + host=self.hostname, + interfaces=[{ + "type": 1, + "main": 1, + "useip": 1, + "ip": "127.0.0.1", + "dns": "", + "port": "10050" + }], + groups=[{"groupid": "2"}] + ) + hostid = created_host['hostids'][0] + + self.assertIsNotNone(hostid, "Creating test host was going wrong") + + items = await zapi.item.get( + filter={'key_': self.itemkey}, + output=['itemid'] + ) + + itemid = None + if len(items) > 0: + itemid = items[0].get('itemid') + + if not itemid: + created_item = await zapi.item.create( + name=self.itemname, + key_=self.itemkey, + hostid=hostid, + type=2, + value_type=3 + ) + itemid = created_item['itemids'][0] + + self.assertIsNotNone(hostid, "Creating test item was going wrong") + + await zapi.logout() + + async def test_send_values(self): + """Tests sending item values""" + + time.sleep(2) + + items = [ + ItemValue(self.hostname, self.itemkey, 10), + ItemValue(self.hostname, self.itemkey, 'test message'), + ItemValue(self.hostname, 'item_key1', -1, 1695713666), + ItemValue(self.hostname, 'item_key2', '{"msg":"test message"}'), + ItemValue(self.hostname, self.itemkey, 0, 1695713666, 100), + ItemValue(self.hostname, self.itemkey, 5.5, 1695713666) + ] + resp = await self.sender.send(items) + self.assertEqual(type(resp), TrapperResponse, "Sending item values was going wrong") + self.assertEqual(resp.total, len(items), "Total number of the sent values is unexpected") + self.assertEqual(resp.processed, 4, "Number of the processed values is unexpected") + self.assertEqual(resp.failed, (resp.total - resp.processed), "Number of the failed values is unexpected") + + first_chunk = list(resp.details.values())[0][0] + self.assertEqual(type(first_chunk), TrapperResponse, "Sending item values was going wrong") + self.assertEqual(first_chunk.total, len(items), "Total number of the sent values is unexpected") + self.assertEqual(first_chunk.processed, 4, "Number of the processed values is unexpected") + self.assertEqual(first_chunk.failed, (first_chunk.total - first_chunk.processed), "Number of the failed values is unexpected") + + +class CompatibilityAsyncGetTest(unittest.IsolatedAsyncioTestCase): + """Compatibility asynchronous test with Zabbix get version 7.0""" + + async def asyncSetUp(self): + self.host = ZABBIX_URL + self.port = 10050 + self.agent = AsyncGetter( + host=self.host, + port=self.port + ) + + async def test_get_values(self): + """Tests getting item values""" + + resp = await self.agent.get('system.uname') + + self.assertIsNotNone(resp, "Getting item values was going wrong") + self.assertEqual(type(resp), AgentResponse, "Got value is unexpected") + self.assertEqual(type(resp.value), str, "Got value is unexpected") + + +if __name__ == '__main__': + unittest.main() diff --git a/.github/workflows/compatibility_70.yaml b/.github/workflows/compatibility_70.yaml new file mode 100644 index 0000000..9955570 --- /dev/null +++ b/.github/workflows/compatibility_70.yaml @@ -0,0 +1,90 @@ +name: zabbix_70 +run-name: Compatibility with Zabbix 7.0 test + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +env: + ZABBIX_VERSION: '7.0' + ZABBIX_BRANCH: release/$ZABBIX_VERSION + CONFIG_PATH: .github/configs/ + TEST_FILE: compatibility_api_test_7.py + +jobs: + compatibility: + name: Compatibility test + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Install packages + run: | + sudo apt update && sudo apt install -y git sudo gcc make automake pkg-config postgresql-14 libpostgresql-ocaml-dev libxml2-dev libpcre3-dev libevent-dev apache2 libapache2-mod-php php8.1-pgsql php8.1-bcmath php8.1-xml php8.1-gd php8.1-ldap php8.1-mbstring libzip-dev + - name: Build from sources + run: | + WORKDIR=$(pwd) + cd /tmp/ + git -c advice.detachedHead=false clone https://git.zabbix.com/scm/zbx/zabbix.git --branch ${{ env.ZABBIX_BRANCH }} --depth 1 --single-branch /tmp/zabbix-branch + cd /tmp/zabbix-branch + ./bootstrap.sh + ./configure --enable-server --enable-agent --with-postgresql + sudo make dbschema_postgresql + sudo make + echo -e "CacheUpdateFrequency=1\n" >> ./conf/zabbix_server.conf + cd ui + sudo rm /var/www/html/index.html + sudo cp -a . /var/www/html/ + sudo cp $WORKDIR/${{ env.CONFIG_PATH }}/zabbix.conf.php /var/www/html/conf/ + sudo cp $WORKDIR/${{ env.CONFIG_PATH }}/pg_hba.conf /etc/postgresql/14/main/pg_hba.conf + sudo chown -R www-data:www-data /var/www/html/ + sudo sed -i "s/post_max_size = 8M/post_max_size = 16M/g" /etc/php/8.1/apache2/php.ini + sudo sed -i "s/max_execution_time = 30/max_execution_time = 300/g" /etc/php/8.1/apache2/php.ini + sudo sed -i "s/max_input_time = 60/max_input_time = 300/g" /etc/php/8.1/apache2/php.ini + sudo locale-gen en_US.UTF-8 + sudo update-locale + - name: Prepare environment + run: | + sudo addgroup --system --quiet zabbix + sudo adduser --quiet --system --disabled-login --ingroup zabbix --home /var/lib/zabbix --no-create-home zabbix + sudo mkdir -p /var/run/postgresql/14-main.pg_stat_tmp + sudo touch /var/run/postgresql/14-main.pg_stat_tmp/global.tmp + sudo chmod 0777 /var/run/postgresql/14-main.pg_stat_tmp/global.tmp + (sudo -u postgres /usr/lib/postgresql/14/bin/postgres -D /var/lib/postgresql/14/main -c config_file=/etc/postgresql/14/main/postgresql.conf)& + sleep 5 + cd /tmp/zabbix-branch/database/postgresql + sudo -u postgres createuser zabbix + sudo -u postgres createdb -O zabbix -E Unicode -T template0 zabbix + cat schema.sql | sudo -u zabbix psql zabbix + cat images.sql | sudo -u zabbix psql zabbix + cat data.sql | sudo -u zabbix psql zabbix + sudo apache2ctl start + - name: Start Zabbix server + run: | + cd /tmp/zabbix-branch + sudo ./src/zabbix_server/zabbix_server -c ./conf/zabbix_server.conf + - name: Start Zabbix agent + run: | + cd /tmp/zabbix-branch + sudo ./src/zabbix_agent/zabbix_agentd -c ./conf/zabbix_agentd.conf + - name: Install python3 + run: | + sudo apt-get install -y python3 python3-pip python-is-python3 + pip install -r ./requirements.txt + - name: Wait for Zabbix API + run: | + python ./.github/scripts/wait_instance_zabbix.py + - name: Compatibility test + continue-on-error: true + run: | + python ./.github/scripts/$TEST_FILE 2>/tmp/compatibility.log >/dev/null + - name: Send report + env: + TBOT_TOKEN: ${{ secrets.TBOT_TOKEN }} + TBOT_CHAT: ${{ vars.TBOT_CHAT }} + SUBJECT: Compatibility with Zabbix ${{ env.ZABBIX_VERSION }} FAIL + run: | + tail -n1 /tmp/compatibility.log | grep "OK" 1>/dev/null || tail /tmp/compatibility.log | python ./.github/scripts/telegram_msg.py | exit 1 diff --git a/README.md b/README.md index 779e0fc..73aa2da 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,11 @@ [![Zabbix API](https://github.com/zabbix/python-zabbix-utils/actions/workflows/integration_api.yaml/badge.svg)](https://github.com/zabbix/python-zabbix-utils/actions/workflows/integration_api.yaml) [![Zabbix sender](https://github.com/zabbix/python-zabbix-utils/actions/workflows/integration_sender.yaml/badge.svg)](https://github.com/zabbix/python-zabbix-utils/actions/workflows/integration_sender.yaml) [![Zabbix get](https://github.com/zabbix/python-zabbix-utils/actions/workflows/integration_getter.yaml/badge.svg)](https://github.com/zabbix/python-zabbix-utils/actions/workflows/integration_getter.yaml) + [![Zabbix 5.0](https://github.com/zabbix/python-zabbix-utils/actions/workflows/compatibility_50.yaml/badge.svg)](https://github.com/zabbix/python-zabbix-utils/actions/workflows/compatibility_50.yaml) [![Zabbix 6.0](https://github.com/zabbix/python-zabbix-utils/actions/workflows/compatibility_60.yaml/badge.svg)](https://github.com/zabbix/python-zabbix-utils/actions/workflows/compatibility_60.yaml) [![Zabbix 6.4](https://github.com/zabbix/python-zabbix-utils/actions/workflows/compatibility_64.yaml/badge.svg)](https://github.com/zabbix/python-zabbix-utils/actions/workflows/compatibility_64.yaml) +[![Zabbix 7.0](https://github.com/zabbix/python-zabbix-utils/actions/workflows/compatibility_70.yaml/badge.svg)](https://github.com/zabbix/python-zabbix-utils/actions/workflows/compatibility_70.yaml) **zabbix_utils** is a Python library for working with [Zabbix API](https://www.zabbix.com/documentation/current/manual/api/reference) as well as with [Zabbix sender](https://www.zabbix.com/documentation/current/manpages/zabbix_sender) and [Zabbix get](https://www.zabbix.com/documentation/current/manpages/zabbix_get) protocols. @@ -27,7 +29,7 @@ Supported versions: Tested on: -* Zabbix 5.0, 6.0, 6.4 and pre-7.0 +* Zabbix 5.0, 6.0, 6.4 and 7.0 * Python 3.8, 3.9, 3.10, 3.11 and 3.12 Dependencies: