diff --git a/.github/scripts/compatibility_api_test_5.py b/.github/scripts/compatibility_api_test_5.py deleted file mode 100644 index 009e2a1..0000000 --- a/.github/scripts/compatibility_api_test_5.py +++ /dev/null @@ -1,385 +0,0 @@ -#!/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, APINotSupported -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 5.0""" - - def setUp(self): - self.url = ZABBIX_URL - self.user = ZABBIX_USER - self.password = ZABBIX_PASSWORD - self.token = 'token' - self.zapi = ZabbixAPI( - url=self.url - ) - - def test_classic_auth(self): - """Tests classic 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") - - users = self.zapi.user.get( - output=['userid', 'name'] - ) - self.assertEqual(type(users), list, "Request user.get 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_token_auth(self): - """Tests auth using token""" - - with self.assertRaises(APINotSupported, - msg="Login by token should be not supported"): - self.zapi.login(token=self.token) - - -class CompatibilitySenderTest(unittest.TestCase): - """Compatibility synchronous test with Zabbix sender version 5.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] - - self.assertIsNotNone(hostid, "Creating test item was going wrong") - - zapi.logout() - - def test_send_values(self): - """Tests sending item values""" - - time.sleep(10) - - 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 5.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 5.0""" - - async def asyncSetUp(self): - self.url = ZABBIX_URL - self.user = ZABBIX_USER - self.password = ZABBIX_PASSWORD - self.token = 'token' - self.zapi = AsyncZabbixAPI( - url=self.url - ) - - async def asyncTearDown(self): - if self.zapi: - await self.zapi.logout() - - async def test_classic_auth(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") - - users = await self.zapi.user.get( - output=['userid', 'name'] - ) - self.assertEqual(type(users), list, "Request user.get 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 '')) - - async def test_token_auth(self): - """Tests auth using token""" - - with self.assertRaises(APINotSupported, - msg="Login by token should be not supported"): - await self.zapi.login(token=self.token) - - -class CompatibilityAsyncSenderTest(unittest.IsolatedAsyncioTestCase): - """Compatibility asynchronous test with Zabbix sender version 5.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(10) - - 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 5.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/scripts/compatibility_api_test_7.py b/.github/scripts/compatibility_api_test_7.py index cb944ee..6818620 100644 --- a/.github/scripts/compatibility_api_test_7.py +++ b/.github/scripts/compatibility_api_test_7.py @@ -25,7 +25,7 @@ class CompatibilityAPITest(unittest.TestCase): - """Compatibility synchronous test with Zabbix API version 7.0, 7.2""" + """Compatibility synchronous test with Zabbix API version 7.0, 7.2 and 7.4""" def setUp(self): self.url = ZABBIX_URL @@ -111,7 +111,7 @@ def test_token_auth(self): class CompatibilitySenderTest(unittest.TestCase): - """Compatibility synchronous test with Zabbix sender version 7.0, 7.2""" + """Compatibility synchronous test with Zabbix sender version 7.0, 7.2 and 7.4""" def setUp(self): self.ip = ZABBIX_URL @@ -286,7 +286,7 @@ def test_send_values(self): class CompatibilityGetTest(unittest.TestCase): - """Compatibility synchronous test with Zabbix get version 7.0, 7.2""" + """Compatibility synchronous test with Zabbix get version 7.0, 7.2 and 7.4""" def setUp(self): self.host = ZABBIX_URL @@ -307,7 +307,7 @@ def test_get_values(self): class CompatibilityAsyncAPITest(unittest.IsolatedAsyncioTestCase): - """Compatibility asynchronous test with Zabbix API version 7.0, 7.2""" + """Compatibility asynchronous test with Zabbix API version 7.0, 7.2 and 7.4""" async def asyncSetUp(self): self.url = ZABBIX_URL @@ -399,7 +399,7 @@ async def test_token_auth(self): class CompatibilityAsyncSenderTest(unittest.IsolatedAsyncioTestCase): - """Compatibility asynchronous test with Zabbix sender version 7.0, 7.2""" + """Compatibility asynchronous test with Zabbix sender version 7.0, 7.2 and 7.4""" async def asyncSetUp(self): self.ip = ZABBIX_URL @@ -580,7 +580,7 @@ async def test_send_values(self): class CompatibilityAsyncGetTest(unittest.IsolatedAsyncioTestCase): - """Compatibility asynchronous test with Zabbix get version 7.0, 7.2""" + """Compatibility asynchronous test with Zabbix get version 7.0, 7.2 and 7.4""" async def asyncSetUp(self): self.host = ZABBIX_URL diff --git a/.github/workflows/additional_tests.yaml b/.github/workflows/additional_tests.yaml index 6d2f56e..5edf088 100644 --- a/.github/workflows/additional_tests.yaml +++ b/.github/workflows/additional_tests.yaml @@ -58,10 +58,10 @@ jobs: test $(cat /tmp/importing.log | wc -l) -eq 0 || exit 1 additional-tests: name: Additional tests - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - name: Install packages run: | sudo apt update && sudo apt install -y git sudo nginx 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 diff --git a/.github/workflows/compatibility_50.yaml b/.github/workflows/compatibility_74.yaml similarity index 54% rename from .github/workflows/compatibility_50.yaml rename to .github/workflows/compatibility_74.yaml index 704f4e4..5f7247b 100644 --- a/.github/workflows/compatibility_50.yaml +++ b/.github/workflows/compatibility_74.yaml @@ -1,5 +1,5 @@ -name: zabbix_50 -run-name: Compatibility with Zabbix 5.0 test +name: zabbix_74 +run-name: Compatibility with Zabbix 7.4 test on: push: @@ -9,28 +9,27 @@ on: workflow_dispatch: env: - ZABBIX_VERSION: '5.0' + ZABBIX_VERSION: '7.4' ZABBIX_BRANCH: release/$ZABBIX_VERSION CONFIG_PATH: .github/configs/ - TEST_FILE: compatibility_api_test_5.py + TEST_FILE: compatibility_api_test_7.py jobs: compatibility: name: Compatibility test - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install packages run: | - curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/postgresql.gpg - echo "deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-pgdg main" | sudo tee /etc/apt/sources.list.d/pgdg.list - sudo add-apt-repository ppa:ondrej/php - 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 libzip-dev php7.4 libapache2-mod-php7.4 php7.4-common php7.4-fpm php7.4-gd php7.4-mbstring php7.4-xml php7.4-zip php7.4-ldap + sudo apt update && sudo apt install -y git sudo gcc make automake pkg-config postgresql-16 libpostgresql-ocaml-dev libxml2-dev libpcre3-dev libevent-dev apache2 libapache2-mod-php php8.3-pgsql php8.3-bcmath php8.3-xml php8.3-gd php8.3-ldap php8.3-mbstring libzip-dev - name: Build from sources run: | WORKDIR=$(pwd) cd /tmp/ + sudo addgroup --system --quiet zabbix + sudo adduser --quiet --system --disabled-login --ingroup zabbix --home /var/lib/zabbix --no-create-home zabbix 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 @@ -38,27 +37,43 @@ jobs: sudo make dbschema_postgresql sudo make echo -e "CacheUpdateFrequency=1\n" >> ./conf/zabbix_server.conf + ./configure --enable-proxy --with-sqlite3 + sudo make + mkdir /tmp/zabbix_proxy1/ + mkdir /tmp/zabbix_proxy2/ + cp ./conf/zabbix_proxy.conf ./conf/zabbix_proxy1.conf + mv ./conf/zabbix_proxy.conf ./conf/zabbix_proxy2.conf + sed -i "s/Hostname=Zabbix proxy/Hostname=CompatibilitySenderTest_proxy1/g" ./conf/zabbix_proxy1.conf + sed -i "s/Hostname=Zabbix proxy/Hostname=CompatibilitySenderTest_proxy2/g" ./conf/zabbix_proxy2.conf + sed -i "s#LogFile=/tmp/zabbix_proxy.log#LogFile=/tmp/zabbix_proxy1.log#g" ./conf/zabbix_proxy1.conf + sed -i "s#LogFile=/tmp/zabbix_proxy.log#LogFile=/tmp/zabbix_proxy2.log#g" ./conf/zabbix_proxy2.conf + sed -i 's#DBName=zabbix_proxy#DBName=/tmp/proxy1.db#' ./conf/zabbix_proxy1.conf + sed -i 's#DBName=zabbix_proxy#DBName=/tmp/proxy2.db#' ./conf/zabbix_proxy2.conf + echo -e "PidFile=/tmp/zabbix_proxy1/zabbix_proxy1.pid\n" >> ./conf/zabbix_proxy1.conf + echo -e "PidFile=/tmp/zabbix_proxy2/zabbix_proxy2.pid\n" >> ./conf/zabbix_proxy2.conf + echo -e "SocketDir=/tmp/zabbix_proxy1\n" >> ./conf/zabbix_proxy1.conf + echo -e "SocketDir=/tmp/zabbix_proxy2\n" >> ./conf/zabbix_proxy2.conf + echo -e "ListenPort=10061\n" >> ./conf/zabbix_proxy1.conf + echo -e "ListenPort=10062\n" >> ./conf/zabbix_proxy2.conf + sudo chown -R zabbix:zabbix /tmp/zabbix_proxy1/ + sudo chown -R zabbix:zabbix /tmp/zabbix_proxy2/ 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 cp $WORKDIR/${{ env.CONFIG_PATH }}/pg_hba.conf /etc/postgresql/16/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/7.4/fpm/php.ini - sudo sed -i "s/max_execution_time = 30/max_execution_time = 300/g" /etc/php/7.4/fpm/php.ini - sudo sed -i "s/max_input_time = 60/max_input_time = 300/g" /etc/php/7.4/fpm/php.ini + sudo sed -i "s/post_max_size = 8M/post_max_size = 16M/g" /etc/php/8.3/apache2/php.ini + sudo sed -i "s/max_execution_time = 30/max_execution_time = 300/g" /etc/php/8.3/apache2/php.ini + sudo sed -i "s/max_input_time = 60/max_input_time = 300/g" /etc/php/8.3/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 chown -R postgres. /var/run/postgresql/ - 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 sed -i 's/port = 5433/port = 5432/' /etc/postgresql/14/main/postgresql.conf - (sudo -u postgres /usr/lib/postgresql/14/bin/postgres -D /var/lib/postgresql/14/main -c config_file=/etc/postgresql/14/main/postgresql.conf)& + sudo mkdir -p /var/run/postgresql/16-main.pg_stat_tmp + sudo touch /var/run/postgresql/16-main.pg_stat_tmp/global.tmp + sudo chmod 0777 /var/run/postgresql/16-main.pg_stat_tmp/global.tmp + (sudo -u postgres /usr/lib/postgresql/16/bin/postgres -D /var/lib/postgresql/16/main -c config_file=/etc/postgresql/16/main/postgresql.conf)& sleep 5 cd /tmp/zabbix-branch/database/postgresql sudo -u postgres createuser zabbix @@ -71,6 +86,12 @@ jobs: run: | cd /tmp/zabbix-branch sudo ./src/zabbix_server/zabbix_server -c ./conf/zabbix_server.conf + - name: Start Zabbix proxies + continue-on-error: true + run: | + cd /tmp/zabbix-branch + sudo ./src/zabbix_proxy/zabbix_proxy -c ./conf/zabbix_proxy1.conf + sudo ./src/zabbix_proxy/zabbix_proxy -c ./conf/zabbix_proxy2.conf - name: Start Zabbix agent run: | cd /tmp/zabbix-branch diff --git a/.github/workflows/integration_api.yaml b/.github/workflows/integration_api.yaml index 6660097..2d0ae01 100644 --- a/.github/workflows/integration_api.yaml +++ b/.github/workflows/integration_api.yaml @@ -27,7 +27,7 @@ jobs: - 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 + sudo apt update && sudo apt install -y git sudo gcc make automake pkg-config postgresql-16 libpostgresql-ocaml-dev libxml2-dev libpcre3-dev libevent-dev apache2 libapache2-mod-php php8.3-pgsql php8.3-bcmath php8.3-xml php8.3-gd php8.3-ldap php8.3-mbstring libzip-dev - name: Build from sources run: | WORKDIR=$(pwd) @@ -42,21 +42,21 @@ jobs: 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 cp $WORKDIR/${{ env.CONFIG_PATH }}/pg_hba.conf /etc/postgresql/16/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 sed -i "s/post_max_size = 8M/post_max_size = 16M/g" /etc/php/8.3/apache2/php.ini + sudo sed -i "s/max_execution_time = 30/max_execution_time = 300/g" /etc/php/8.3/apache2/php.ini + sudo sed -i "s/max_input_time = 60/max_input_time = 300/g" /etc/php/8.3/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)& + sudo mkdir -p /var/run/postgresql/16-main.pg_stat_tmp + sudo touch /var/run/postgresql/16-main.pg_stat_tmp/global.tmp + sudo chmod 0777 /var/run/postgresql/16-main.pg_stat_tmp/global.tmp + (sudo -u postgres /usr/lib/postgresql/16/bin/postgres -D /var/lib/postgresql/16/main -c config_file=/etc/postgresql/16/main/postgresql.conf)& sleep 1 cd /tmp/zabbix-branch/database/postgresql sudo -u postgres createuser zabbix diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index f259dc6..5fa33d8 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: '3.10' + python-version: '3.12' - name: Get pip cache id: pip-cache run: | diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 41c3e6c..bc8c944 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -18,6 +18,7 @@ jobs: - "3.10" - "3.11" - "3.12" + - "3.13" platform: - ubuntu-latest - macos-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fa06b8..e7c7138 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ +## [2.0.3](https://github.com/zabbix/python-zabbix-utils/compare/v2.0.2...v2.0.3) (2025-07-03) + +### Features: + +- added support for Zabbix 7.4 + +### Changes: + +- discontinued support for Zabbix 5.0 +- added examples of TLS-PSK use for Python 3.13+ + +### Bug fixes: + +- fixed issue [#32](https://github.com/zabbix/python-zabbix-utils/issues/32) with a circular reference of exceptions +- fixed issue [#30](https://github.com/zabbix/python-zabbix-utils/issues/30) by adding Hostname support to Sender and AsyncSender +- fixed issue [#18](https://github.com/zabbix/python-zabbix-utils/issues/18) by adding with-statement support to AsyncZabbixAPI +- fixed issue [#7](https://github.com/zabbix/python-zabbix-utils/issues/7) with the TLS-PSK example code for Python 3.13+ by using built-in SSL features +- fixed small bugs and flaws + ## [2.0.2](https://github.com/zabbix/python-zabbix-utils/compare/v2.0.1...v2.0.2) (2024-12-12) ### Features: diff --git a/README.md b/README.md index 58a61c1..8371718 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,10 @@ [![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 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 7.2](https://github.com/zabbix/python-zabbix-utils/actions/workflows/compatibility_72.yaml/badge.svg)](https://github.com/zabbix/python-zabbix-utils/actions/workflows/compatibility_72.yaml) +[![Zabbix 7.4](https://github.com/zabbix/python-zabbix-utils/actions/workflows/compatibility_74.yaml/badge.svg)](https://github.com/zabbix/python-zabbix-utils/actions/workflows/compatibility_74.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. @@ -24,13 +24,13 @@ Supported versions: -* Zabbix 5.0+ +* Zabbix 6.0+ * Python 3.8+ Tested on: -* Zabbix 5.0, 6.0, 7.0 and 7.2 -* Python 3.8, 3.9, 3.10, 3.11 and 3.12 +* Zabbix 6.0, 7.0, 7.2 and 7.4 +* Python 3.8, 3.9, 3.10, 3.11, 3.12 and 3.13 Dependencies: diff --git a/examples/get/asynchronous/tls_psk_context.py b/examples/get/asynchronous/tls_psk_context.py new file mode 100644 index 0000000..7b74248 --- /dev/null +++ b/examples/get/asynchronous/tls_psk_context.py @@ -0,0 +1,71 @@ +# Copyright (C) 2001-2023 Zabbix SIA +# +# Zabbix SIA licenses this file to you under the MIT License. +# See the LICENSE file in the project root for more information. + +import ssl +import asyncio +from zabbix_utils import AsyncGetter + +# !!! IMPORTANT +# The code example below is supported only from Python version 3.13 onwards. + +# Pre-Shared Key (PSK) and PSK Identity +PSK_KEY = bytes.fromhex('608b0a0049d41fdb35a824ef0a227f24e5099c60aa935e803370a961c937d6f7') +PSK_IDENTITY = b'PSKID' + +# Zabbix agent parameters +ZABBIX_AGENT = "127.0.0.1" +ZABBIX_PORT = 10050 + + +# Create and configure an SSL context for secure communication with the Zabbix server. +def custom_context(*args, **kwargs) -> ssl.SSLContext: + # Create an SSL context for TLS client + context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + + # Disable hostname verification + context.check_hostname = False + + # Set the verification mode to require a valid certificate + context.verify_mode = ssl.CERT_NONE + + # Set the maximum allowed version of the TLS protocol to TLS 1.2 + context.maximum_version = ssl.TLSVersion.TLSv1_2 + + # Set the ciphers to use for the connection + context.set_ciphers('PSK') + + # Set up the callback function to provide the PSK and identity when requested + context.set_psk_client_callback(lambda hint: (PSK_IDENTITY, PSK_KEY)) + + # Return the customized SSL context + return context + + +async def main(): + """ + The main function to perform asynchronous tasks. + """ + + # Create a AsyncGetter instance with a custom SSL context + agent = AsyncGetter( + host=ZABBIX_AGENT, + port=ZABBIX_PORT, + ssl_context=custom_context + ) + + # Send a Zabbix agent query for system information (e.g., uname) + resp = await agent.get('system.uname') + + # Check if there was an error in the response + if resp.error: + # Print the error message + print("An error occurred while trying to get the value:", resp.error) + else: + # Print the value obtained for the specified item key item + print("Received value:", resp.value) + + +# Run the main coroutine +asyncio.run(main()) diff --git a/examples/get/synchronous/psk_wrapper.py b/examples/get/synchronous/psk_wrapper.py index 8551088..4c0e5bc 100644 --- a/examples/get/synchronous/psk_wrapper.py +++ b/examples/get/synchronous/psk_wrapper.py @@ -6,6 +6,10 @@ import ssl from zabbix_utils import Getter +# !!! IMPORTANT +# The following code example is supposed to be used with Python up to the 3.12 version. +# Starting with Python 3.13, TLS-PSK is supported by the built-in ssl module. + # Try importing sslpsk3, fall back to sslpsk2 if not available try: import sslpsk3 as sslpsk @@ -13,6 +17,10 @@ # Import sslpsk2 if sslpsk3 is not available import sslpsk2 as sslpsk +# Zabbix agent parameters +ZABBIX_AGENT = "127.0.0.1" +ZABBIX_PORT = 10050 + # PSK wrapper function for SSL connection def psk_wrapper(sock): @@ -29,10 +37,6 @@ def psk_wrapper(sock): ) -# Zabbix agent parameters -ZABBIX_AGENT = "127.0.0.1" -ZABBIX_PORT = 10050 - # Create a Getter instance with PSK support agent = Getter( host=ZABBIX_AGENT, diff --git a/examples/get/synchronous/tls_psk_wrapper.py b/examples/get/synchronous/tls_psk_wrapper.py new file mode 100644 index 0000000..b24af6a --- /dev/null +++ b/examples/get/synchronous/tls_psk_wrapper.py @@ -0,0 +1,61 @@ +# Copyright (C) 2001-2023 Zabbix SIA +# +# Zabbix SIA licenses this file to you under the MIT License. +# See the LICENSE file in the project root for more information. + +import ssl +from zabbix_utils import Getter + +# !!! IMPORTANT +# The code example below is supported only from Python version 3.13 onwards. + +# Zabbix agent parameters +ZABBIX_AGENT = "127.0.0.1" +ZABBIX_PORT = 10050 + + +# PSK wrapper function for SSL connection +def psk_wrapper(sock): + # Pre-Shared Key (PSK) and PSK Identity + psk = bytes.fromhex('608b0a0049d41fdb35a824ef0a227f24e5099c60aa935e803370a961c937d6f7') + psk_identity = b'PSKID' + + # Create an SSL context for TLS client + context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + + # Disable hostname verification + context.check_hostname = False + + # Set the verification mode to require a valid certificate + context.verify_mode = ssl.CERT_NONE + + # Set the maximum allowed version of the TLS protocol to TLS 1.2 + context.maximum_version = ssl.TLSVersion.TLSv1_2 + + # Set the ciphers to use for the connection + context.set_ciphers('PSK') + + # Set up the callback function to provide the PSK and identity when requested + context.set_psk_client_callback(lambda hint: (psk_identity, psk)) + + # Wrap the socket to establish an SSL connection with PSK + return context.wrap_socket(sock) + + +# Create a Getter instance with PSK support +agent = Getter( + host=ZABBIX_AGENT, + port=ZABBIX_PORT, + socket_wrapper=psk_wrapper +) + +# Send a Zabbix agent query for system information (e.g., uname) +resp = agent.get('system.uname') + +# Check if there was an error in the response +if resp.error: + # Print the error message + print("An error occurred while trying to get the value:", resp.error) +else: + # Print the value obtained for the specified item key item + print("Received value:", resp.value) diff --git a/examples/sender/asynchronous/agent_config_using.py b/examples/sender/asynchronous/agent_config_using.py index ce259c9..b315597 100644 --- a/examples/sender/asynchronous/agent_config_using.py +++ b/examples/sender/asynchronous/agent_config_using.py @@ -22,7 +22,7 @@ async def main(): # Send a value to a Zabbix server/proxy with specified parameters # Parameters: (host, key, value, clock) - response = await sender.send_value('host', 'item.key', 'value', 1695713666) + response = await sender.send_value(sender.host, 'item.key', 'value', 1695713666) # Check if the value sending was successful if response.failed == 0: diff --git a/examples/sender/asynchronous/tls_cert_context_from_config.py b/examples/sender/asynchronous/tls_cert_context_from_config.py index 072e6d0..9260c77 100644 --- a/examples/sender/asynchronous/tls_cert_context_from_config.py +++ b/examples/sender/asynchronous/tls_cert_context_from_config.py @@ -48,6 +48,7 @@ async def main(): sender = AsyncSender( server=ZABBIX_SERVER, port=ZABBIX_PORT, + use_config=True, ssl_context=custom_context ) diff --git a/examples/sender/asynchronous/tls_psk_context.py b/examples/sender/asynchronous/tls_psk_context.py new file mode 100644 index 0000000..e5977d7 --- /dev/null +++ b/examples/sender/asynchronous/tls_psk_context.py @@ -0,0 +1,71 @@ +# Copyright (C) 2001-2023 Zabbix SIA +# +# Zabbix SIA licenses this file to you under the MIT License. +# See the LICENSE file in the project root for more information. + +import ssl +import asyncio +from zabbix_utils import AsyncSender + +# !!! IMPORTANT +# The code example below is supported only from Python version 3.13 onwards. + +# Zabbix server details +ZABBIX_SERVER = "zabbix-server.example.com" +ZABBIX_PORT = 10051 + +# Pre-Shared Key (PSK) and PSK Identity +PSK_KEY = bytes.fromhex('608b0a0049d41fdb35a824ef0a227f24e5099c60aa935e803370a961c937d6f7') +PSK_IDENTITY = b'PSKID' + + +# Create and configure an SSL context for secure communication with the Zabbix server. +def custom_context(*args, **kwargs) -> ssl.SSLContext: + # Create an SSL context for TLS client + context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + + # Disable hostname verification + context.check_hostname = False + + # Set the verification mode to require a valid certificate + context.verify_mode = ssl.CERT_NONE + + # Set the maximum allowed version of the TLS protocol to TLS 1.2 + context.maximum_version = ssl.TLSVersion.TLSv1_2 + + # Set the ciphers to use for the connection + context.set_ciphers('PSK') + + # Set up the callback function to provide the PSK and identity when requested + context.set_psk_client_callback(lambda hint: (PSK_IDENTITY, PSK_KEY)) + + # Return the customized SSL context + return context + + +async def main(): + """ + The main function to perform asynchronous tasks. + """ + + # Create an instance of AsyncSender with a custom SSL context + sender = AsyncSender( + server=ZABBIX_SERVER, + port=ZABBIX_PORT, + ssl_context=custom_context + ) + + # Send a value to a Zabbix server/proxy with specified parameters + # Parameters: (host, key, value, clock, ns) + response = await sender.send_value('host', 'item.key', 'value', 1695713666, 30) + + # Check if the value sending was successful + if response.failed == 0: + # Print a success message along with the response time + print(f"Value sent successfully in {response.time}") + else: + # Print a failure message + print("Failed to send value") + +# Run the main coroutine +asyncio.run(main()) diff --git a/examples/sender/asynchronous/tls_psk_context_from_config.py b/examples/sender/asynchronous/tls_psk_context_from_config.py new file mode 100644 index 0000000..a5a4a89 --- /dev/null +++ b/examples/sender/asynchronous/tls_psk_context_from_config.py @@ -0,0 +1,79 @@ +# Copyright (C) 2001-2023 Zabbix SIA +# +# Zabbix SIA licenses this file to you under the MIT License. +# See the LICENSE file in the project root for more information. + +import ssl +import asyncio +from zabbix_utils import AsyncSender + +# !!! IMPORTANT +# The code example below is supported only from Python version 3.13 onwards. + +# Zabbix server details +ZABBIX_SERVER = "zabbix-server.example.com" +ZABBIX_PORT = 10051 + + +# Create and configure an SSL context for secure communication with the Zabbix server. +def custom_context(config) -> ssl.SSLContext: + psk = None + + # Try to get PSK key and identity + psk_identity = config.get('tlspskidentity').encode('utf-8') + psk_file = config.get('tlspskfile') + + # Read PSK from file if specified + if psk_file: + with open(psk_file, encoding='utf-8') as f: + psk = bytes.fromhex(f.read()) + + # Create an SSL context for TLS client + context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + + # Disable hostname verification + context.check_hostname = False + + # Set the verification mode to require a valid certificate + context.verify_mode = ssl.CERT_NONE + + # Set the maximum allowed version of the TLS protocol to TLS 1.2 + context.maximum_version = ssl.TLSVersion.TLSv1_2 + + # Set the ciphers to use for the connection + context.set_ciphers('PSK') + + # Set up the callback function to provide the PSK and identity when requested + context.set_psk_client_callback(lambda hint: (psk_identity, psk)) + + # Return the customized SSL context + return context + + +async def main(): + """ + The main function to perform asynchronous tasks. + """ + + # Create an instance of AsyncSender with a custom SSL context + sender = AsyncSender( + server=ZABBIX_SERVER, + port=ZABBIX_PORT, + use_config=True, + ssl_context=custom_context + ) + + # Send a value to a Zabbix server/proxy with specified parameters + # Parameters: (host, key, value, clock, ns) + response = await sender.send_value('host', 'item.key', 'value', 1695713666, 30) + + # Check if the value sending was successful + if response.failed == 0: + # Print a success message along with the response time + print(f"Value sent successfully in {response.time}") + else: + # Print a failure message + print("Failed to send value") + +# Run the main coroutine +asyncio.run(main()) diff --git a/examples/sender/synchronous/agent_config_using.py b/examples/sender/synchronous/agent_config_using.py index 1bdea04..cfba28a 100644 --- a/examples/sender/synchronous/agent_config_using.py +++ b/examples/sender/synchronous/agent_config_using.py @@ -15,7 +15,7 @@ # Send a value to a Zabbix server/proxy with specified parameters # Parameters: (host, key, value, clock) -response = sender.send_value('host', 'item.key', 'value', 1695713666) +response = sender.send_value(sender.host, 'item.key', 'value', 1695713666) # Check if the value sending was successful if response.failed == 0: diff --git a/examples/sender/synchronous/psk_wrapper.py b/examples/sender/synchronous/psk_wrapper.py index 9dea96e..1e8057e 100644 --- a/examples/sender/synchronous/psk_wrapper.py +++ b/examples/sender/synchronous/psk_wrapper.py @@ -6,6 +6,10 @@ import ssl from zabbix_utils import Sender +# !!! IMPORTANT +# The following code example is supposed to be used with Python up to the 3.12 version. +# Starting with Python 3.13, TLS-PSK is supported by the built-in ssl module. + # Try importing sslpsk3, fall back to sslpsk2 if not available try: import sslpsk3 as sslpsk @@ -15,7 +19,7 @@ # PSK wrapper function for SSL connection -def psk_wrapper(sock, tls): +def psk_wrapper(sock, *args, **kwargs): # Pre-Shared Key (PSK) and PSK Identity psk = bytes.fromhex('608b0a0049d41fdb35a824ef0a227f24e5099c60aa935e803370a961c937d6f7') psk_identity = b'PSKID' diff --git a/examples/sender/synchronous/tls_psk_wrapper.py b/examples/sender/synchronous/tls_psk_wrapper.py new file mode 100644 index 0000000..d6d22aa --- /dev/null +++ b/examples/sender/synchronous/tls_psk_wrapper.py @@ -0,0 +1,62 @@ +# Copyright (C) 2001-2023 Zabbix SIA +# +# Zabbix SIA licenses this file to you under the MIT License. +# See the LICENSE file in the project root for more information. + +import ssl +from zabbix_utils import Sender + +# !!! IMPORTANT +# The code example below is supported only from Python version 3.13 onwards. + +# Zabbix server details +ZABBIX_SERVER = "zabbix-server.example.com" +ZABBIX_PORT = 10051 + + +# PSK wrapper function for SSL connection +def psk_wrapper(sock, *args, **kwargs): + # Pre-Shared Key (PSK) and PSK Identity + psk = bytes.fromhex('608b0a0049d41fdb35a824ef0a227f24e5099c60aa935e803370a961c937d6f7') + psk_identity = b'PSKID' + + # Create an SSL context for TLS client + context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + + # Disable hostname verification + context.check_hostname = False + + # Set the verification mode to require a valid certificate + context.verify_mode = ssl.CERT_NONE + + # Set the maximum allowed version of the TLS protocol to TLS 1.2 + context.maximum_version = ssl.TLSVersion.TLSv1_2 + + # Set the ciphers to use for the connection + context.set_ciphers('PSK') + + # Set up the callback function to provide the PSK and identity when requested + context.set_psk_client_callback(lambda hint: (psk_identity, psk)) + + # Wrap the socket to establish an SSL connection with PSK + return context.wrap_socket(sock) + + +# Create a Sender instance with PSK support +sender = Sender( + server=ZABBIX_SERVER, + port=ZABBIX_PORT, + socket_wrapper=psk_wrapper +) + +# Send a value to a Zabbix server/proxy with specified parameters +# Parameters: (host, key, value, clock, ns) +response = sender.send_value('host', 'item.key', 'value', 1695713666, 30) + +# Check if the value sending was successful +if response.failed == 0: + # Print a success message along with the response time + print(f"Value sent successfully in {response.time}") +else: + # Print a failure message + print("Failed to send value") diff --git a/examples/sender/synchronous/tls_psk_wrapper_from_config.py b/examples/sender/synchronous/tls_psk_wrapper_from_config.py new file mode 100644 index 0000000..9a70d67 --- /dev/null +++ b/examples/sender/synchronous/tls_psk_wrapper_from_config.py @@ -0,0 +1,73 @@ +# Copyright (C) 2001-2023 Zabbix SIA +# +# Zabbix SIA licenses this file to you under the MIT License. +# See the LICENSE file in the project root for more information. + +import ssl +from zabbix_utils import Sender + +# !!! IMPORTANT +# The code example below is supported only from Python version 3.13 onwards. + +# Zabbix server details +ZABBIX_SERVER = "zabbix-server.example.com" +ZABBIX_PORT = 10051 + + +# PSK wrapper function for SSL connection +def psk_wrapper(sock, config): + psk = None + psk_identity = config.get('tlspskidentity').encode('utf-8') + psk_file = config.get('tlspskfile') + + # Read PSK from file if specified + if psk_file: + with open(psk_file, encoding='utf-8') as f: + psk = bytes.fromhex(f.read()) + + # Check if both PSK and PSK identity are available + if psk and psk_identity: + # Create an SSL context for TLS client + context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + + # Disable hostname verification + context.check_hostname = False + + # Set the verification mode to require a valid certificate + context.verify_mode = ssl.CERT_NONE + + # Set the maximum allowed version of the TLS protocol to TLS 1.2 + context.maximum_version = ssl.TLSVersion.TLSv1_2 + + # Set the ciphers to use for the connection + context.set_ciphers('PSK') + + # Set up the callback function to provide the PSK and identity when requested + context.set_psk_client_callback(lambda hint: (psk_identity, psk)) + + # Wrap the socket to establish an SSL connection with PSK + return context.wrap_socket(sock) + + # Return original socket if PSK or PSK identity is missing + return sock + + +# Create a Sender instance with PSK support +sender = Sender( + server=ZABBIX_SERVER, + port=ZABBIX_PORT, + use_config=True, + socket_wrapper=psk_wrapper +) + +# Send a value to a Zabbix server/proxy with specified parameters +# Parameters: (host, key, value, clock, ns) +response = sender.send_value('host', 'item.key', 'value', 1695713666, 30) + +# Check if the value sending was successful +if response.failed == 0: + # Print a success message along with the response time + print(f"Value sent successfully in {response.time}") +else: + # Print a failure message + print("Failed to send value") diff --git a/setup.py b/setup.py index 5cb887b..31a240f 100644 --- a/setup.py +++ b/setup.py @@ -65,6 +65,7 @@ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Topic :: System :: Monitoring", diff --git a/tests/test_zabbix_aioapi.py b/tests/test_zabbix_aioapi.py index 96f1417..4d9b278 100644 --- a/tests/test_zabbix_aioapi.py +++ b/tests/test_zabbix_aioapi.py @@ -164,21 +164,32 @@ async def test_login(self): self.assertEqual(self.zapi._AsyncZabbixAPI__session_id, case['output'], f"unexpected output with input data: {case['input']}") await self.zapi.logout() - - async with AsyncZabbixAPI(client_session=common.MockSession()) as zapi: - try: - await zapi.login(**case['input']) - except case['exception']: - if not case['raised']: - self.fail(f"raised unexpected Exception with input data: {case['input']}") - else: - if case['raised']: - self.fail(f"not raised expected Exception with input data: {case['input']}") + try: + zapi = await AsyncZabbixAPI(client_session=common.MockSession(), **case['input']) + self.assertEqual(zapi._AsyncZabbixAPI__use_token, bool(case['input'].get('token')), + f"unexpected output with input data: {case['input']}") + self.assertEqual(zapi._AsyncZabbixAPI__session_id, case['output'], + f"unexpected output with input data: {case['input']}") + except case['exception']: + if not case['raised']: + self.fail(f"raised unexpected Exception with input data: {case['input']}") + else: + if case['raised']: + self.fail(f"not raised expected Exception with input data: {case['input']}") + + try: + async with AsyncZabbixAPI(client_session=common.MockSession(), **case['input']) as zapi: self.assertEqual(zapi._AsyncZabbixAPI__use_token, bool(case['input'].get('token')), f"unexpected output with input data: {case['input']}") self.assertEqual(zapi._AsyncZabbixAPI__session_id, case['output'], f"unexpected output with input data: {case['input']}") + except case['exception']: + if not case['raised']: + self.fail(f"raised unexpected Exception with input data: {case['input']}") + else: + if case['raised']: + self.fail(f"not raised expected Exception with input data: {case['input']}") async def test_logout(self): """Tests logout in different auth cases""" @@ -375,37 +386,43 @@ async def test_version_conditions(self): test_cases = [ { 'input': {'token': DEFAULT_VALUES['token']}, - 'version': '5.2.0', - 'raised': {'APINotSupported': True, 'ProcessingError': True}, + 'version': '5.0.0', + 'raised': {'APINotSupported': True, 'ProcessingError': False}, 'output': DEFAULT_VALUES['session'] }, + { + 'input': {'token': DEFAULT_VALUES['token']}, + 'version': '6.0.1', + 'raised': {'APINotSupported': False, 'ProcessingError': False}, + 'output': DEFAULT_VALUES['token'] + }, { 'input': {'token': DEFAULT_VALUES['token'], 'user': DEFAULT_VALUES['user'], 'password': DEFAULT_VALUES['password']}, - 'version': '5.2.0', + 'version': '6.0.2', 'raised': {'APINotSupported': True, 'ProcessingError': True}, 'output': DEFAULT_VALUES['session'] }, { 'input': {'user': DEFAULT_VALUES['user'], 'password': DEFAULT_VALUES['password']}, - 'version': '5.2.0', + 'version': '6.0.3', 'raised': {'APINotSupported': False, 'ProcessingError': False}, 'output': DEFAULT_VALUES['session'] }, { 'input': {'token': DEFAULT_VALUES['token']}, - 'version': '5.4.0', + 'version': '7.4.1', 'raised': {'APINotSupported': False, 'ProcessingError': False}, 'output': DEFAULT_VALUES['token'] }, { 'input': {'token': DEFAULT_VALUES['token'], 'user': DEFAULT_VALUES['user'], 'password': DEFAULT_VALUES['password']}, - 'version': '5.4.0', + 'version': '7.4.2', 'raised': {'APINotSupported': False, 'ProcessingError': True}, 'output': DEFAULT_VALUES['token'] }, { 'input': {'user': DEFAULT_VALUES['user'], 'password': DEFAULT_VALUES['password']}, - 'version': '5.4.0', + 'version': '7.4.3', 'raised': {'APINotSupported': False, 'ProcessingError': False}, 'output': DEFAULT_VALUES['session'] } diff --git a/tests/test_zabbix_api.py b/tests/test_zabbix_api.py index 63ec234..4be1c7f 100644 --- a/tests/test_zabbix_api.py +++ b/tests/test_zabbix_api.py @@ -286,37 +286,43 @@ def test_version_conditions(self): test_cases = [ { 'input': {'token': DEFAULT_VALUES['token']}, - 'version': '5.2.0', - 'raised': {'APINotSupported': True, 'ProcessingError': True}, + 'version': '5.0.0', + 'raised': {'APINotSupported': True, 'ProcessingError': False}, 'output': DEFAULT_VALUES['session'] }, + { + 'input': {'token': DEFAULT_VALUES['token']}, + 'version': '6.0.1', + 'raised': {'APINotSupported': False, 'ProcessingError': False}, + 'output': DEFAULT_VALUES['token'] + }, { 'input': {'token': DEFAULT_VALUES['token'], 'user': DEFAULT_VALUES['user'], 'password': DEFAULT_VALUES['password']}, - 'version': '5.2.0', + 'version': '6.0.2', 'raised': {'APINotSupported': True, 'ProcessingError': True}, 'output': DEFAULT_VALUES['session'] }, { 'input': {'user': DEFAULT_VALUES['user'], 'password': DEFAULT_VALUES['password']}, - 'version': '5.2.0', + 'version': '6.0.3', 'raised': {'APINotSupported': False, 'ProcessingError': False}, 'output': DEFAULT_VALUES['session'] }, { 'input': {'token': DEFAULT_VALUES['token']}, - 'version': '5.4.0', + 'version': '7.4.1', 'raised': {'APINotSupported': False, 'ProcessingError': False}, 'output': DEFAULT_VALUES['token'] }, { 'input': {'token': DEFAULT_VALUES['token'], 'user': DEFAULT_VALUES['user'], 'password': DEFAULT_VALUES['password']}, - 'version': '5.4.0', + 'version': '7.4.2', 'raised': {'APINotSupported': False, 'ProcessingError': True}, 'output': DEFAULT_VALUES['token'] }, { 'input': {'user': DEFAULT_VALUES['user'], 'password': DEFAULT_VALUES['password']}, - 'version': '5.4.0', + 'version': '7.4.3', 'raised': {'APINotSupported': False, 'ProcessingError': False}, 'output': DEFAULT_VALUES['session'] } diff --git a/zabbix_utils/aioapi.py b/zabbix_utils/aioapi.py index ccf411d..a52f00e 100644 --- a/zabbix_utils/aioapi.py +++ b/zabbix_utils/aioapi.py @@ -34,7 +34,7 @@ from os import environ as env from urllib.error import URLError -from typing import Callable, Union, Optional, Any +from typing import Callable, Union, Optional, Coroutine, Any from aiohttp.client_exceptions import ContentTypeError from .types import APIVersion @@ -120,7 +120,8 @@ class AsyncZabbixAPI(): __session_id = None __internal_client = None - def __init__(self, url: Optional[str] = None, + def __init__(self, url: Optional[str] = None, token: Optional[str] = None, + user: Optional[str] = None, password: Optional[str] = None, http_user: Optional[str] = None, http_password: Optional[str] = None, skip_version_check: bool = False, validate_certs: bool = True, client_session: Optional[aiohttp.ClientSession] = None, timeout: int = 30): @@ -131,6 +132,10 @@ def __init__(self, url: Optional[str] = None, self.validate_certs = validate_certs self.timeout = timeout + self.__token = token + self.__user = user + self.__password = password + client_params: dict = {} if client_session is None: @@ -171,18 +176,21 @@ def __getattr__(self, name: str) -> Callable: return APIObject(name, self) async def __aenter__(self) -> Callable: - return self + return await self.login() async def __aexit__(self, *args) -> None: await self.logout() + def __await__(self) -> Coroutine: + return self.login().__await__() + async def __aclose_session(self) -> None: if self.__internal_client: await self.__internal_client.close() async def __exception(self, exc) -> None: await self.__aclose_session() - raise exc from exc + raise exc def __close_session(self) -> None: if self.__internal_client: @@ -212,7 +220,7 @@ def version(self) -> APIVersion: return self.api_version() async def login(self, token: Optional[str] = None, user: Optional[str] = None, - password: Optional[str] = None) -> None: + password: Optional[str] = None) -> Callable: """Login to Zabbix API. Args: @@ -221,9 +229,9 @@ async def login(self, token: Optional[str] = None, user: Optional[str] = None, password (str, optional): Zabbix API user's password. Defaults to `None`. """ - user = user or env.get('ZABBIX_USER') or None - password = password or env.get('ZABBIX_PASSWORD') or None - token = token or env.get('ZABBIX_TOKEN') or None + token = token or self.__token or env.get('ZABBIX_TOKEN') or None + user = user or self.__user or env.get('ZABBIX_USER') or None + password = password or self.__password or env.get('ZABBIX_PASSWORD') or None if token: if self.version < 5.4: @@ -237,7 +245,7 @@ async def login(self, token: Optional[str] = None, user: Optional[str] = None, ) self.__use_token = True self.__session_id = token - return + return self if not user: await self.__exception(ProcessingError("Username is missing")) @@ -263,6 +271,8 @@ async def login(self, token: Optional[str] = None, user: Optional[str] = None, log.debug("Connected to Zabbix API version %s: %s", self.version, self.url) + return self + async def logout(self) -> None: """Logout from Zabbix API.""" diff --git a/zabbix_utils/aiosender.py b/zabbix_utils/aiosender.py index 6e51d4e..03c2584 100644 --- a/zabbix_utils/aiosender.py +++ b/zabbix_utils/aiosender.py @@ -69,6 +69,7 @@ def __init__(self, server: Optional[str] = None, port: int = 10051, self.use_ipv6 = use_ipv6 self.tls = {} + self.host = None self.source_ip = None self.chunk_size = chunk_size self.compression = compression @@ -105,6 +106,7 @@ def __read_config(self, config: configparser.SectionProxy) -> None: for cluster in server_row.split(','): self.clusters.append(Cluster(cluster.strip().split(';'))) + self.host = config.get('Hostname') if 'SourceIP' in config: self.source_ip = config.get('SourceIP') @@ -310,4 +312,4 @@ async def send_value(self, host: str, key: str, TrapperResponse: Response from Zabbix server/proxy. """ - return await self.send([ItemValue(host, key, value, clock, ns)]) + return await self.send([ItemValue(host or self.host or '', key, value, clock, ns)]) diff --git a/zabbix_utils/sender.py b/zabbix_utils/sender.py index cb34ca8..0d99ef8 100644 --- a/zabbix_utils/sender.py +++ b/zabbix_utils/sender.py @@ -66,6 +66,7 @@ def __init__(self, server: Optional[str] = None, port: int = 10051, self.use_ipv6 = use_ipv6 self.tls = {} + self.host = None self.source_ip = None self.chunk_size = chunk_size self.compression = compression @@ -102,6 +103,7 @@ def __read_config(self, config: configparser.SectionProxy) -> None: for cluster in server_row.split(','): self.clusters.append(Cluster(cluster.strip().split(';'))) + self.host = config.get('Hostname') if 'SourceIP' in config: self.source_ip = config.get('SourceIP') @@ -218,7 +220,6 @@ def __send_to_cluster(self, cluster: Cluster, packet: bytes) -> Optional[Tuple[N if response and response.get('response') != 'success': if response.get('redirect'): - print(response) log.debug( 'Packet was redirected from %s to %s. Proxy group revision: %s.', active_node, @@ -312,4 +313,4 @@ def send_value(self, host: str, key: str, TrapperResponse: Response from Zabbix server/proxy. """ - return self.send([ItemValue(host, key, value, clock, ns)]) + return self.send([ItemValue(host or self.host or '', key, value, clock, ns)]) diff --git a/zabbix_utils/version.py b/zabbix_utils/version.py index 15c3e95..2d1f19d 100644 --- a/zabbix_utils/version.py +++ b/zabbix_utils/version.py @@ -22,7 +22,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. -__version__ = "2.0.2" +__version__ = "2.0.3" -__min_supported__ = 5.0 -__max_supported__ = 7.2 +__min_supported__ = 6.0 +__max_supported__ = 7.4