diff --git a/.github/scripts/additional_api_tests.py b/.github/scripts/additional_api_tests.py
index 1ffe373..2ad8661 100644
--- a/.github/scripts/additional_api_tests.py
+++ b/.github/scripts/additional_api_tests.py
@@ -6,7 +6,6 @@
import sys
import ssl
-import base64
import unittest
from aiohttp import ClientSession, TCPConnector
@@ -18,77 +17,6 @@
ZABBIX_URL = 'https://127.0.0.1:443'
ZABBIX_USER = 'Admin'
ZABBIX_PASSWORD = 'zabbix'
-HTTP_USER = 'http_user'
-HTTP_PASSWORD = 'http_pass'
-
-
-class IntegrationAPITest(unittest.TestCase):
- """Test working with a real Zabbix API instance synchronously"""
-
- def setUp(self):
- self.user = ZABBIX_USER
- self.password = ZABBIX_PASSWORD
- self.url = ZABBIX_URL + '/http_auth/'
- self.api = ZabbixAPI(
- url=self.url,
- user=self.user,
- password=self.password,
- skip_version_check=True,
- validate_certs=False,
- http_user=HTTP_USER,
- http_password=HTTP_PASSWORD
- )
-
- def tearDown(self):
- if self.api:
- self.api.logout()
-
- def test_login(self):
- """Tests login function works properly"""
-
- self.assertEqual(
- type(self.api), ZabbixAPI, "Login was going wrong")
- self.assertEqual(
- type(self.api.api_version()), APIVersion, "Version getting was going wrong")
-
- def test_basic_auth(self):
- """Tests __basic_auth function works properly"""
-
- self.assertEqual(
- self.api._ZabbixAPI__basic_cred, base64.b64encode(
- "http_user:http_pass".encode()
- ).decode(), "Basic auth credentials generation was going wrong")
-
- def test_version_get(self):
- """Tests getting version info works properly"""
-
- version = None
- if self.api:
- version = self.api.apiinfo.version()
- self.assertEqual(
- version, str(self.api.api_version()), "Request apiinfo.version was going wrong")
-
- def test_check_auth(self):
- """Tests checking authentication state works properly"""
-
- resp = None
- if self.api:
- if self.api._ZabbixAPI__session_id == self.api._ZabbixAPI__token:
- resp = self.api.user.checkAuthentication(token=self.api._ZabbixAPI__session_id)
- else:
- resp = self.api.user.checkAuthentication(sessionid=self.api._ZabbixAPI__session_id)
- self.assertEqual(
- type(resp), dict, "Request user.checkAuthentication was going wrong")
-
- def test_user_get(self):
- """Tests getting users info works properly"""
-
- users = None
- if self.api:
- users = self.api.user.get(
- output=['userid', 'name']
- )
- self.assertEqual(type(users), list, "Request user.get was going wrong")
class CustomCertAPITest(unittest.TestCase):
@@ -154,80 +82,6 @@ def test_user_get(self):
self.assertEqual(type(users), list, "Request user.get was going wrong")
-class IntegrationAsyncAPITest(unittest.IsolatedAsyncioTestCase):
- """Test working with a real Zabbix API instance asynchronously"""
-
- async def asyncSetUp(self):
- self.user = ZABBIX_USER
- self.password = ZABBIX_PASSWORD
- self.url = ZABBIX_URL + '/http_auth/'
- self.api = AsyncZabbixAPI(
- url=self.url,
- skip_version_check=True,
- validate_certs=False,
- http_user=HTTP_USER,
- http_password=HTTP_PASSWORD
- )
- await self.api.login(
- user=self.user,
- password=self.password
- )
-
- async def asyncTearDown(self):
- if self.api:
- await self.api.logout()
-
- async def test_login(self):
- """Tests login function works properly"""
-
- self.assertEqual(
- type(self.api), AsyncZabbixAPI, "Login was going wrong")
- self.assertEqual(
- type(self.api.api_version()), APIVersion, "Version getting was going wrong")
-
- async def test_basic_auth(self):
- """Tests __basic_auth function works properly"""
-
- basic_auth = self.api.client_session._default_auth
-
- self.assertEqual(
- base64.b64encode(f"{basic_auth.login}:{basic_auth.password}".encode()).decode(),
- base64.b64encode(f"{HTTP_USER}:{HTTP_PASSWORD}".encode()).decode(),
- "Basic auth credentials generation was going wrong"
- )
-
- async def test_version_get(self):
- """Tests getting version info works properly"""
-
- version = None
- if self.api:
- version = await self.api.apiinfo.version()
- self.assertEqual(
- version, str(self.api.api_version()), "Request apiinfo.version was going wrong")
-
- async def test_check_auth(self):
- """Tests checking authentication state works properly"""
-
- resp = None
- if self.api:
- if self.api._AsyncZabbixAPI__session_id == self.api._AsyncZabbixAPI__token:
- resp = await self.api.user.checkAuthentication(token=(self.api._AsyncZabbixAPI__session_id or ''))
- else:
- resp = await self.api.user.checkAuthentication(sessionid=(self.api._AsyncZabbixAPI__session_id or ''))
- self.assertEqual(
- type(resp), dict, "Request user.checkAuthentication was going wrong")
-
- async def test_user_get(self):
- """Tests getting users info works properly"""
-
- users = None
- if self.api:
- users = await self.api.user.get(
- output=['userid', 'name']
- )
- self.assertEqual(type(users), list, "Request user.get was going wrong")
-
-
class CustomCertAsyncAPITest(unittest.IsolatedAsyncioTestCase):
"""Test working with a real Zabbix API instance asynchronously"""
@@ -238,14 +92,14 @@ async def asyncSetUp(self):
context = ssl.create_default_context()
context.load_verify_locations('/etc/nginx/ssl/nginx.crt')
- session = ClientSession(
+ self.session = ClientSession(
connector=TCPConnector(ssl=context)
)
self.api = AsyncZabbixAPI(
url=self.url,
skip_version_check=True,
- client_session=session
+ client_session=self.session
)
await self.api.login(
user=self.user,
@@ -255,6 +109,8 @@ async def asyncSetUp(self):
async def asyncTearDown(self):
if self.api:
await self.api.logout()
+ if not self.session.closed:
+ await self.session.close()
async def test_login(self):
"""Tests login function works properly"""
diff --git a/.github/scripts/compatibility_api_test_5.py b/.github/scripts/compatibility_api_test_5.py
index b9ef881..009e2a1 100644
--- a/.github/scripts/compatibility_api_test_5.py
+++ b/.github/scripts/compatibility_api_test_5.py
@@ -147,8 +147,6 @@ def prepare_items(self):
value_type=3
)['itemids'][0]
- time.sleep(2)
-
self.assertIsNotNone(hostid, "Creating test item was going wrong")
zapi.logout()
@@ -156,6 +154,8 @@ def prepare_items(self):
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'),
@@ -337,7 +337,7 @@ async def prepare_items(self):
async def test_send_values(self):
"""Tests sending item values"""
- time.sleep(2)
+ time.sleep(10)
items = [
ItemValue(self.hostname, self.itemkey, 10),
diff --git a/.github/scripts/compatibility_api_test_6.py b/.github/scripts/compatibility_api_test_6.py
index 2219ef8..5808851 100644
--- a/.github/scripts/compatibility_api_test_6.py
+++ b/.github/scripts/compatibility_api_test_6.py
@@ -179,8 +179,6 @@ def prepare_items(self):
value_type=3
)['itemids'][0]
- time.sleep(2)
-
self.assertIsNotNone(hostid, "Creating test item was going wrong")
zapi.logout()
@@ -188,6 +186,8 @@ def prepare_items(self):
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'),
@@ -403,7 +403,7 @@ async def prepare_items(self):
async def test_send_values(self):
"""Tests sending item values"""
- time.sleep(2)
+ time.sleep(10)
items = [
ItemValue(self.hostname, self.itemkey, 10),
diff --git a/.github/scripts/compatibility_api_test_7.py b/.github/scripts/compatibility_api_test_7.py
index ba85b4e..cb944ee 100644
--- a/.github/scripts/compatibility_api_test_7.py
+++ b/.github/scripts/compatibility_api_test_7.py
@@ -21,10 +21,11 @@
ZABBIX_URL = '127.0.0.1'
ZABBIX_USER = 'Admin'
ZABBIX_PASSWORD = 'zabbix'
+ZABBIX_PROXY_ADDR = '127.0.0.1'
class CompatibilityAPITest(unittest.TestCase):
- """Compatibility synchronous test with Zabbix API version 7.0"""
+ """Compatibility synchronous test with Zabbix API version 7.0, 7.2"""
def setUp(self):
self.url = ZABBIX_URL
@@ -110,7 +111,7 @@ def test_token_auth(self):
class CompatibilitySenderTest(unittest.TestCase):
- """Compatibility synchronous test with Zabbix sender version 7.0"""
+ """Compatibility synchronous test with Zabbix sender version 7.0, 7.2"""
def setUp(self):
self.ip = ZABBIX_URL
@@ -121,32 +122,42 @@ def setUp(self):
port=self.port,
chunk_size=self.chunk_size
)
+ self.zapi = None
+ self.hostid = None
+ self.proxy_groupid = None
+ self.proxy_ip = ZABBIX_PROXY_ADDR
self.hostname = f"{self.__class__.__name__}_host"
self.itemname = f"{self.__class__.__name__}_item"
self.itemkey = f"{self.__class__.__name__}"
- self.prepare_items()
+ self.pgroupname = "CompatibilitySenderTest_group"
+ self.proxy = "CompatibilitySenderTest_proxy"
+ self.proxyids = []
+ self.prepare_instance()
- def prepare_items(self):
- """Creates host and items for sending values later"""
+ def tearDown(self):
+ if self.zapi:
+ self.zapi.logout()
+
+ def prepare_instance(self):
+ """Creates required entities for sending values later"""
- zapi = ZabbixAPI(
+ self.zapi = ZabbixAPI(
url=ZABBIX_URL,
user=ZABBIX_USER,
password=ZABBIX_PASSWORD,
skip_version_check=True
)
- hosts = zapi.host.get(
+ hosts = self.zapi.host.get(
filter={'host': self.hostname},
output=['hostid']
)
- hostid = None
if len(hosts) > 0:
- hostid = hosts[0].get('hostid')
+ self.hostid = hosts[0].get('hostid')
- if not hostid:
- hostid = zapi.host.create(
+ if not self.hostid:
+ self.hostid = self.zapi.host.create(
host=self.hostname,
interfaces=[{
"type": 1,
@@ -159,9 +170,9 @@ def prepare_items(self):
groups=[{"groupid": "2"}]
)['hostids'][0]
- self.assertIsNotNone(hostid, "Creating test host was going wrong")
+ self.assertIsNotNone(self.hostid, "Creating test host was going wrong")
- items = zapi.item.get(
+ items = self.zapi.item.get(
filter={'key_': self.itemkey},
output=['itemid']
)
@@ -171,23 +182,68 @@ def prepare_items(self):
itemid = items[0].get('itemid')
if not itemid:
- itemid = zapi.item.create(
+ itemid = self.zapi.item.create(
name=self.itemname,
key_=self.itemkey,
- hostid=hostid,
+ hostid=self.hostid,
type=2,
value_type=3
)['itemids'][0]
time.sleep(2)
- self.assertIsNotNone(hostid, "Creating test item was going wrong")
+ self.assertIsNotNone(itemid, "Creating test item was going wrong")
+
+ groups = self.zapi.proxygroup.get(
+ filter={'name': self.pgroupname},
+ output=['proxy_groupid']
+ )
+
+ if len(groups) > 0:
+ self.proxy_groupid = groups[0].get('proxy_groupid')
- zapi.logout()
+ if not self.proxy_groupid:
+ self.proxy_groupid = self.zapi.proxygroup.create(
+ name=self.pgroupname,
+ failover_delay="10s",
+ min_online="1"
+ )['proxy_groupids'][0]
+
+ self.assertIsNotNone(self.proxy_groupid, "Creating test proxy group was going wrong")
+
+ time.sleep(10)
+
+ proxies = self.zapi.proxy.get(
+ search={'name': self.proxy},
+ output=['proxyid']
+ )
+ if len(proxies) > 0:
+ self.zapi.proxy.delete(*[p['proxyid'] for p in proxies])
+
+ self.proxyids += self.zapi.proxy.create(
+ name=self.proxy + "1",
+ operating_mode="0",
+ local_address=self.proxy_ip,
+ local_port=10061,
+ proxy_groupid=self.proxy_groupid
+ )['proxyids']
+ self.proxyids += self.zapi.proxy.create(
+ name=self.proxy + "2",
+ operating_mode="0",
+ local_address=self.proxy_ip,
+ local_port=10062,
+ proxy_groupid=self.proxy_groupid
+ )['proxyids']
+
+ self.assertTrue(bool(self.proxyids), "Creating test proxy group was going wrong")
def test_send_values(self):
"""Tests sending item values"""
+ self.zapi.host.update(
+ hostid=self.hostid,
+ monitored_by="0"
+ )
items = [
ItemValue(self.hostname, self.itemkey, 10),
ItemValue(self.hostname, self.itemkey, 'test message'),
@@ -208,9 +264,29 @@ def test_send_values(self):
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")
+ for port in [10061, 10062]:
+ self.sender = Sender(
+ server=self.proxy_ip,
+ port=port,
+ chunk_size=self.chunk_size
+ )
+ self.zapi.host.update(
+ hostid=self.hostid,
+ monitored_by="2",
+ proxy_groupid=self.proxy_groupid
+ )
+ resp = self.sender.send_value(self.hostname, self.itemkey, 10)
+
+ self.assertEqual(type(resp), TrapperResponse, "Sending item values was going wrong")
+ self.assertEqual(resp.total, 1, "Total number of the sent 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, 1, "Total number of the sent values is unexpected")
+
class CompatibilityGetTest(unittest.TestCase):
- """Compatibility synchronous test with Zabbix get version 7.0"""
+ """Compatibility synchronous test with Zabbix get version 7.0, 7.2"""
def setUp(self):
self.host = ZABBIX_URL
@@ -231,7 +307,7 @@ def test_get_values(self):
class CompatibilityAsyncAPITest(unittest.IsolatedAsyncioTestCase):
- """Compatibility asynchronous test with Zabbix API version 7.0"""
+ """Compatibility asynchronous test with Zabbix API version 7.0, 7.2"""
async def asyncSetUp(self):
self.url = ZABBIX_URL
@@ -323,7 +399,7 @@ async def test_token_auth(self):
class CompatibilityAsyncSenderTest(unittest.IsolatedAsyncioTestCase):
- """Compatibility asynchronous test with Zabbix sender version 7.0"""
+ """Compatibility asynchronous test with Zabbix sender version 7.0, 7.2"""
async def asyncSetUp(self):
self.ip = ZABBIX_URL
@@ -334,34 +410,42 @@ async def asyncSetUp(self):
port=self.port,
chunk_size=self.chunk_size
)
+ self.zapi = None
+ self.hostid = None
+ self.proxy_groupid = None
+ self.proxy_ip = ZABBIX_PROXY_ADDR
self.hostname = f"{self.__class__.__name__}_host"
self.itemname = f"{self.__class__.__name__}_item"
self.itemkey = f"{self.__class__.__name__}"
+ self.pgroupname = "CompatibilitySenderTest_group"
+ self.proxy = "CompatibilitySenderTest_proxy"
+ self.proxyids = []
await self.prepare_items()
+ async def asyncTearDown(self):
+ if self.zapi:
+ self.zapi.logout()
+
async def prepare_items(self):
- """Creates host and items for sending values later"""
+ """Creates required entities for sending values later"""
- zapi = AsyncZabbixAPI(
+ self.zapi = ZabbixAPI(
url=ZABBIX_URL,
- skip_version_check=True
- )
- await zapi.login(
user=ZABBIX_USER,
- password=ZABBIX_PASSWORD
+ password=ZABBIX_PASSWORD,
+ skip_version_check=True
)
- hosts = await zapi.host.get(
+ hosts = self.zapi.host.get(
filter={'host': self.hostname},
output=['hostid']
)
- hostid = None
if len(hosts) > 0:
- hostid = hosts[0].get('hostid')
+ self.hostid = hosts[0].get('hostid')
- if not hostid:
- created_host = await zapi.host.create(
+ if not self.hostid:
+ created_host = self.zapi.host.create(
host=self.hostname,
interfaces=[{
"type": 1,
@@ -373,11 +457,11 @@ async def prepare_items(self):
}],
groups=[{"groupid": "2"}]
)
- hostid = created_host['hostids'][0]
+ self.hostid = created_host['hostids'][0]
- self.assertIsNotNone(hostid, "Creating test host was going wrong")
+ self.assertIsNotNone(self.hostid, "Creating test host was going wrong")
- items = await zapi.item.get(
+ items = self.zapi.item.get(
filter={'key_': self.itemkey},
output=['itemid']
)
@@ -387,23 +471,72 @@ async def prepare_items(self):
itemid = items[0].get('itemid')
if not itemid:
- created_item = await zapi.item.create(
+ created_item = self.zapi.item.create(
name=self.itemname,
key_=self.itemkey,
- hostid=hostid,
+ hostid=self.hostid,
type=2,
value_type=3
)
itemid = created_item['itemids'][0]
- self.assertIsNotNone(hostid, "Creating test item was going wrong")
+ self.assertIsNotNone(itemid, "Creating test item was going wrong")
+
+ groups = self.zapi.proxygroup.get(
+ filter={'name': self.pgroupname},
+ output=['proxy_groupid']
+ )
+
+ if len(groups) > 0:
+ self.proxy_groupid = groups[0].get('proxy_groupid')
- await zapi.logout()
+ if not self.proxy_groupid:
+ created_proxy_group = self.zapi.proxygroup.create(
+ name=self.pgroupname,
+ failover_delay="10s",
+ min_online="1"
+ )
+ self.proxy_groupid = created_proxy_group['proxy_groupids'][0]
+
+ self.assertIsNotNone(self.proxy_groupid, "Creating test proxy group was going wrong")
+
+ time.sleep(10)
+
+ proxies = self.zapi.proxy.get(
+ search={'name': self.proxy},
+ output=['proxyid']
+ )
+ if len(proxies) > 0:
+ self.zapi.proxy.delete(*[p['proxyid'] for p in proxies])
+
+ created_proxy = self.zapi.proxy.create(
+ name=self.proxy + "1",
+ operating_mode="0",
+ local_address=self.proxy_ip,
+ local_port=10061,
+ proxy_groupid=self.proxy_groupid
+ )
+ self.proxyids += created_proxy['proxyids']
+ created_proxy = self.zapi.proxy.create(
+ name=self.proxy + "2",
+ operating_mode="0",
+ local_address=self.proxy_ip,
+ local_port=10062,
+ proxy_groupid=self.proxy_groupid
+ )
+ self.proxyids += created_proxy['proxyids']
+
+ self.assertTrue(bool(self.proxyids), "Creating test proxy group was going wrong")
async def test_send_values(self):
"""Tests sending item values"""
- time.sleep(2)
+ self.zapi.host.update(
+ hostid=self.hostid,
+ monitored_by="0"
+ )
+
+ time.sleep(5)
items = [
ItemValue(self.hostname, self.itemkey, 10),
@@ -425,9 +558,29 @@ async def test_send_values(self):
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")
+ for port in [10061, 10062]:
+ self.sender = AsyncSender(
+ server=self.proxy_ip,
+ port=port,
+ chunk_size=self.chunk_size
+ )
+ self.zapi.host.update(
+ hostid=self.hostid,
+ monitored_by="2",
+ proxy_groupid=self.proxy_groupid
+ )
+ resp = await self.sender.send_value(self.hostname, self.itemkey, 10)
+
+ self.assertEqual(type(resp), TrapperResponse, "Sending item values was going wrong")
+ self.assertEqual(resp.total, 1, "Total number of the sent 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, 1, "Total number of the sent values is unexpected")
+
class CompatibilityAsyncGetTest(unittest.IsolatedAsyncioTestCase):
- """Compatibility asynchronous test with Zabbix get version 7.0"""
+ """Compatibility asynchronous test with Zabbix get version 7.0, 7.2"""
async def asyncSetUp(self):
self.host = ZABBIX_URL
diff --git a/.github/scripts/compatibility_api_test_latest.py b/.github/scripts/compatibility_api_test_latest.py
index 05f5fcb..daad262 100644
--- a/.github/scripts/compatibility_api_test_latest.py
+++ b/.github/scripts/compatibility_api_test_latest.py
@@ -21,6 +21,7 @@
ZABBIX_URL = '127.0.0.1'
ZABBIX_USER = 'Admin'
ZABBIX_PASSWORD = 'zabbix'
+ZABBIX_PROXY_ADDR = '127.0.0.1'
class CompatibilityAPITest(unittest.TestCase):
@@ -122,32 +123,42 @@ def setUp(self):
port=self.port,
chunk_size=self.chunk_size
)
+ self.zapi = None
+ self.hostid = None
+ self.proxy_groupid = None
+ self.proxy_ip = ZABBIX_PROXY_ADDR
self.hostname = f"{self.__class__.__name__}_host"
self.itemname = f"{self.__class__.__name__}_item"
self.itemkey = f"{self.__class__.__name__}"
- self.prepare_items()
+ self.pgroupname = "CompatibilitySenderTest_group"
+ self.proxy = "CompatibilitySenderTest_proxy"
+ self.proxyids = []
+ self.prepare_instance()
- def prepare_items(self):
- """Creates host and items for sending values later"""
+ def tearDown(self):
+ if self.zapi:
+ self.zapi.logout()
+
+ def prepare_instance(self):
+ """Creates required entities for sending values later"""
- zapi = ZabbixAPI(
+ self.zapi = ZabbixAPI(
url=ZABBIX_URL,
user=ZABBIX_USER,
password=ZABBIX_PASSWORD,
skip_version_check=True
)
- hosts = zapi.host.get(
+ hosts = self.zapi.host.get(
filter={'host': self.hostname},
output=['hostid']
)
- hostid = None
if len(hosts) > 0:
- hostid = hosts[0].get('hostid')
+ self.hostid = hosts[0].get('hostid')
- if not hostid:
- hostid = zapi.host.create(
+ if not self.hostid:
+ self.hostid = self.zapi.host.create(
host=self.hostname,
interfaces=[{
"type": 1,
@@ -160,9 +171,9 @@ def prepare_items(self):
groups=[{"groupid": "2"}]
)['hostids'][0]
- self.assertIsNotNone(hostid, "Creating test host was going wrong")
+ self.assertIsNotNone(self.hostid, "Creating test host was going wrong")
- items = zapi.item.get(
+ items = self.zapi.item.get(
filter={'key_': self.itemkey},
output=['itemid']
)
@@ -172,23 +183,68 @@ def prepare_items(self):
itemid = items[0].get('itemid')
if not itemid:
- itemid = zapi.item.create(
+ itemid = self.zapi.item.create(
name=self.itemname,
key_=self.itemkey,
- hostid=hostid,
+ hostid=self.hostid,
type=2,
value_type=3
)['itemids'][0]
- time.sleep(2)
+ time.sleep(2)
+
+ self.assertIsNotNone(itemid, "Creating test item was going wrong")
- self.assertIsNotNone(hostid, "Creating test item was going wrong")
+ groups = self.zapi.proxygroup.get(
+ filter={'name': self.pgroupname},
+ output=['proxy_groupid']
+ )
- zapi.logout()
+ if len(groups) > 0:
+ self.proxy_groupid = groups[0].get('proxy_groupid')
+
+ if not self.proxy_groupid:
+ self.proxy_groupid = self.zapi.proxygroup.create(
+ name=self.pgroupname,
+ failover_delay="10s",
+ min_online="1"
+ )['proxy_groupids'][0]
+
+ self.assertIsNotNone(self.proxy_groupid, "Creating test proxy group was going wrong")
+
+ time.sleep(10)
+
+ proxies = self.zapi.proxy.get(
+ search={'name': self.proxy},
+ output=['proxyid']
+ )
+ if len(proxies) > 0:
+ self.zapi.proxy.delete(*[p['proxyid'] for p in proxies])
+
+ self.proxyids += self.zapi.proxy.create(
+ name=self.proxy + "1",
+ operating_mode="0",
+ local_address=self.proxy_ip,
+ local_port=10061,
+ proxy_groupid=self.proxy_groupid
+ )['proxyids']
+ self.proxyids += self.zapi.proxy.create(
+ name=self.proxy + "2",
+ operating_mode="0",
+ local_address=self.proxy_ip,
+ local_port=10062,
+ proxy_groupid=self.proxy_groupid
+ )['proxyids']
+
+ self.assertTrue(bool(self.proxyids), "Creating test proxy group was going wrong")
def test_send_values(self):
"""Tests sending item values"""
+ self.zapi.host.update(
+ hostid=self.hostid,
+ monitored_by="0"
+ )
items = [
ItemValue(self.hostname, self.itemkey, 10),
ItemValue(self.hostname, self.itemkey, 'test message'),
@@ -209,6 +265,26 @@ def test_send_values(self):
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")
+ for port in [10061, 10062]:
+ self.sender = Sender(
+ server=self.proxy_ip,
+ port=port,
+ chunk_size=self.chunk_size
+ )
+ self.zapi.host.update(
+ hostid=self.hostid,
+ monitored_by="2",
+ proxy_groupid=self.proxy_groupid
+ )
+ resp = self.sender.send_value(self.hostname, self.itemkey, 10)
+
+ self.assertEqual(type(resp), TrapperResponse, "Sending item values was going wrong")
+ self.assertEqual(resp.total, 1, "Total number of the sent 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, 1, "Total number of the sent values is unexpected")
+
class CompatibilityGetTest(unittest.TestCase):
"""Compatibility synchronous test with the latest Zabbix get version"""
@@ -336,34 +412,42 @@ async def asyncSetUp(self):
port=self.port,
chunk_size=self.chunk_size
)
+ self.zapi = None
+ self.hostid = None
+ self.proxy_groupid = None
+ self.proxy_ip = ZABBIX_PROXY_ADDR
self.hostname = f"{self.__class__.__name__}_host"
self.itemname = f"{self.__class__.__name__}_item"
self.itemkey = f"{self.__class__.__name__}"
+ self.pgroupname = "CompatibilitySenderTest_group"
+ self.proxy = "CompatibilitySenderTest_proxy"
+ self.proxyids = []
await self.prepare_items()
+ async def asyncTearDown(self):
+ if self.zapi:
+ self.zapi.logout()
+
async def prepare_items(self):
- """Creates host and items for sending values later"""
+ """Creates required entities for sending values later"""
- zapi = AsyncZabbixAPI(
+ self.zapi = ZabbixAPI(
url=ZABBIX_URL,
- skip_version_check=True
- )
- await zapi.login(
user=ZABBIX_USER,
- password=ZABBIX_PASSWORD
+ password=ZABBIX_PASSWORD,
+ skip_version_check=True
)
- hosts = await zapi.host.get(
+ hosts = self.zapi.host.get(
filter={'host': self.hostname},
output=['hostid']
)
- hostid = None
if len(hosts) > 0:
- hostid = hosts[0].get('hostid')
+ self.hostid = hosts[0].get('hostid')
- if not hostid:
- created_host = await zapi.host.create(
+ if not self.hostid:
+ created_host = self.zapi.host.create(
host=self.hostname,
interfaces=[{
"type": 1,
@@ -375,11 +459,11 @@ async def prepare_items(self):
}],
groups=[{"groupid": "2"}]
)
- hostid = created_host['hostids'][0]
+ self.hostid = created_host['hostids'][0]
- self.assertIsNotNone(hostid, "Creating test host was going wrong")
+ self.assertIsNotNone(self.hostid, "Creating test host was going wrong")
- items = await zapi.item.get(
+ items = self.zapi.item.get(
filter={'key_': self.itemkey},
output=['itemid']
)
@@ -389,23 +473,72 @@ async def prepare_items(self):
itemid = items[0].get('itemid')
if not itemid:
- created_item = await zapi.item.create(
+ created_item = self.zapi.item.create(
name=self.itemname,
key_=self.itemkey,
- hostid=hostid,
+ hostid=self.hostid,
type=2,
value_type=3
)
itemid = created_item['itemids'][0]
- self.assertIsNotNone(hostid, "Creating test item was going wrong")
+ self.assertIsNotNone(itemid, "Creating test item was going wrong")
+
+ groups = self.zapi.proxygroup.get(
+ filter={'name': self.pgroupname},
+ output=['proxy_groupid']
+ )
+
+ if len(groups) > 0:
+ self.proxy_groupid = groups[0].get('proxy_groupid')
+
+ if not self.proxy_groupid:
+ created_proxy_group = self.zapi.proxygroup.create(
+ name=self.pgroupname,
+ failover_delay="10s",
+ min_online="1"
+ )
+ self.proxy_groupid = created_proxy_group['proxy_groupids'][0]
+
+ self.assertIsNotNone(self.proxy_groupid, "Creating test proxy group was going wrong")
+
+ time.sleep(10)
+
+ proxies = self.zapi.proxy.get(
+ search={'name': self.proxy},
+ output=['proxyid']
+ )
+ if len(proxies) > 0:
+ self.zapi.proxy.delete(*[p['proxyid'] for p in proxies])
+
+ created_proxy = self.zapi.proxy.create(
+ name=self.proxy + "1",
+ operating_mode="0",
+ local_address=self.proxy_ip,
+ local_port=10061,
+ proxy_groupid=self.proxy_groupid
+ )
+ self.proxyids += created_proxy['proxyids']
+ created_proxy = self.zapi.proxy.create(
+ name=self.proxy + "2",
+ operating_mode="0",
+ local_address=self.proxy_ip,
+ local_port=10062,
+ proxy_groupid=self.proxy_groupid
+ )
+ self.proxyids += created_proxy['proxyids']
- await zapi.logout()
+ self.assertTrue(bool(self.proxyids), "Creating test proxy group was going wrong")
async def test_send_values(self):
"""Tests sending item values"""
- time.sleep(2)
+ self.zapi.host.update(
+ hostid=self.hostid,
+ monitored_by="0"
+ )
+
+ time.sleep(5)
items = [
ItemValue(self.hostname, self.itemkey, 10),
@@ -427,6 +560,26 @@ async def test_send_values(self):
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")
+ for port in [10061, 10062]:
+ self.sender = AsyncSender(
+ server=self.proxy_ip,
+ port=port,
+ chunk_size=self.chunk_size
+ )
+ self.zapi.host.update(
+ hostid=self.hostid,
+ monitored_by="2",
+ proxy_groupid=self.proxy_groupid
+ )
+ resp = await self.sender.send_value(self.hostname, self.itemkey, 10)
+
+ self.assertEqual(type(resp), TrapperResponse, "Sending item values was going wrong")
+ self.assertEqual(resp.total, 1, "Total number of the sent 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, 1, "Total number of the sent values is unexpected")
+
class CompatibilityAsyncGetTest(unittest.IsolatedAsyncioTestCase):
"""Compatibility asynchronous test with the latest Zabbix get version"""
diff --git a/.github/scripts/depricated_tests.py b/.github/scripts/depricated_tests.py
new file mode 100644
index 0000000..2113cc3
--- /dev/null
+++ b/.github/scripts/depricated_tests.py
@@ -0,0 +1,171 @@
+#!/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 base64
+import unittest
+
+sys.path.append('.')
+from zabbix_utils.api import ZabbixAPI
+from zabbix_utils.types import APIVersion
+from zabbix_utils.aioapi import AsyncZabbixAPI
+
+ZABBIX_URL = 'https://127.0.0.1:443'
+ZABBIX_USER = 'Admin'
+ZABBIX_PASSWORD = 'zabbix'
+HTTP_USER = 'http_user'
+HTTP_PASSWORD = 'http_pass'
+
+
+class BasicAuthAPITest(unittest.TestCase):
+ """Test working with a real Zabbix API instance using Basic auth synchronously
+
+ Should be removed after: `June 30, 2029`
+ """
+
+ def setUp(self):
+ self.user = ZABBIX_USER
+ self.password = ZABBIX_PASSWORD
+ self.url = ZABBIX_URL + '/http_auth/'
+ self.api = ZabbixAPI(
+ url=self.url,
+ user=self.user,
+ password=self.password,
+ validate_certs=False,
+ http_user=HTTP_USER,
+ http_password=HTTP_PASSWORD
+ )
+
+ def tearDown(self):
+ if self.api:
+ self.api.logout()
+
+ def test_login(self):
+ """Tests login function works properly"""
+
+ self.assertEqual(
+ type(self.api), ZabbixAPI, "Login was going wrong")
+ self.assertEqual(
+ type(self.api.api_version()), APIVersion, "Version getting was going wrong")
+
+ def test_basic_auth(self):
+ """Tests __basic_auth function works properly"""
+
+ self.assertEqual(
+ self.api._ZabbixAPI__basic_cred, base64.b64encode(
+ "http_user:http_pass".encode()
+ ).decode(), "Basic auth credentials generation was going wrong")
+
+ def test_version_get(self):
+ """Tests getting version info works properly"""
+
+ version = None
+ if self.api:
+ version = self.api.apiinfo.version()
+ self.assertEqual(
+ version, str(self.api.api_version()), "Request apiinfo.version was going wrong")
+
+ def test_check_auth(self):
+ """Tests checking authentication state works properly"""
+
+ resp = None
+ if self.api:
+ if self.api._ZabbixAPI__session_id == self.api._ZabbixAPI__token:
+ resp = self.api.user.checkAuthentication(token=self.api._ZabbixAPI__session_id)
+ else:
+ resp = self.api.user.checkAuthentication(sessionid=self.api._ZabbixAPI__session_id)
+ self.assertEqual(
+ type(resp), dict, "Request user.checkAuthentication was going wrong")
+
+ def test_user_get(self):
+ """Tests getting users info works properly"""
+
+ users = None
+ if self.api:
+ users = self.api.user.get(
+ output=['userid', 'name']
+ )
+ self.assertEqual(type(users), list, "Request user.get was going wrong")
+
+
+class BasicAuthAsyncAPITest(unittest.IsolatedAsyncioTestCase):
+ """Test working with a real Zabbix API instance using Basic auth asynchronously
+
+ Should be removed after: `June 30, 2029`
+ """
+
+ async def asyncSetUp(self):
+ self.user = ZABBIX_USER
+ self.password = ZABBIX_PASSWORD
+ self.url = ZABBIX_URL + '/http_auth/'
+ self.api = AsyncZabbixAPI(
+ url=self.url,
+ validate_certs=False,
+ http_user=HTTP_USER,
+ http_password=HTTP_PASSWORD
+ )
+ await self.api.login(
+ user=self.user,
+ password=self.password
+ )
+
+ async def asyncTearDown(self):
+ if self.api:
+ await self.api.logout()
+
+ async def test_login(self):
+ """Tests login function works properly"""
+
+ self.assertEqual(
+ type(self.api), AsyncZabbixAPI, "Login was going wrong")
+ self.assertEqual(
+ type(self.api.api_version()), APIVersion, "Version getting was going wrong")
+
+ async def test_basic_auth(self):
+ """Tests __basic_auth function works properly"""
+
+ basic_auth = self.api.client_session._default_auth
+
+ self.assertEqual(
+ base64.b64encode(f"{basic_auth.login}:{basic_auth.password}".encode()).decode(),
+ base64.b64encode(f"{HTTP_USER}:{HTTP_PASSWORD}".encode()).decode(),
+ "Basic auth credentials generation was going wrong"
+ )
+
+ async def test_version_get(self):
+ """Tests getting version info works properly"""
+
+ version = None
+ if self.api:
+ version = await self.api.apiinfo.version()
+ self.assertEqual(
+ version, str(self.api.api_version()), "Request apiinfo.version was going wrong")
+
+ async def test_check_auth(self):
+ """Tests checking authentication state works properly"""
+
+ resp = None
+ if self.api:
+ if self.api._AsyncZabbixAPI__session_id == self.api._AsyncZabbixAPI__token:
+ resp = await self.api.user.checkAuthentication(token=(self.api._AsyncZabbixAPI__session_id or ''))
+ else:
+ resp = await self.api.user.checkAuthentication(sessionid=(self.api._AsyncZabbixAPI__session_id or ''))
+ self.assertEqual(
+ type(resp), dict, "Request user.checkAuthentication was going wrong")
+
+ async def test_user_get(self):
+ """Tests getting users info works properly"""
+
+ users = None
+ if self.api:
+ users = await self.api.user.get(
+ output=['userid', 'name']
+ )
+ self.assertEqual(type(users), list, "Request user.get was going wrong")
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/.github/scripts/release_notification.py b/.github/scripts/release_notification.py
new file mode 100644
index 0000000..65971af
--- /dev/null
+++ b/.github/scripts/release_notification.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python
+# coding: utf-8
+
+import os
+import json
+import smtplib
+import markdown
+
+from email.mime.text import MIMEText
+from email.mime.multipart import MIMEMultipart
+
+# Repository constants
+LIBRARY_VERSION = os.environ['LIBRARY_VERSION']
+REPOSITORY = os.environ['REPOSITORY']
+
+# Mail server variables
+mail_port = int(os.environ.get('MAIL_PORT', '465'))
+mail_server = os.environ['MAIL_SERVER']
+auth_user = os.environ['MAIL_USER']
+auth_pass = os.environ['MAIL_PASS']
+
+# Mail variables
+mail_from = '"zabbix_utils" <' + auth_user + '>'
+mail_to = json.loads(os.environ['RELEASE_RECIPIENT_LIST'])
+mail_subject = f"[GitHub] A new version {LIBRARY_VERSION} of the zabbix_utils library has been released"
+mail_text = f"""
+ A new version of the zabbix_utils library has been released:
+ v{LIBRARY_VERSION}
+
+
+"""
+
+# Reading release notes
+with open("RELEASE_NOTES.md", "r", encoding="utf-8") as fh:
+ release_notes = markdown.markdown("\n".join(fh.readlines()[1:]))
+
+# Preparing mail data
+msg = MIMEMultipart('mixed')
+msg['Subject'] = mail_subject
+msg['From'] = mail_from
+msg['To'] = ', '.join(mail_to)
+
+# Adding message text
+msg.attach(MIMEText(mail_text + release_notes, 'html'))
+
+# Connection to the mail server
+server = smtplib.SMTP_SSL(mail_server, mail_port)
+server.login(auth_user, auth_pass)
+
+# Sending email
+server.sendmail(mail_from, mail_to, msg.as_string())
+
+# Closing connection
+server.quit()
diff --git a/.github/workflows/additional_tests.yaml b/.github/workflows/additional_tests.yaml
index be6bc60..1478992 100644
--- a/.github/workflows/additional_tests.yaml
+++ b/.github/workflows/additional_tests.yaml
@@ -52,7 +52,7 @@ jobs:
TBOT_CHAT: ${{ vars.TBOT_CHAT }}
SUBJECT: Importing tests with requirements FAIL
run: |
- bash ./.github/scripts/library_import_tests.sh "async" "AsyncZabbixAPI" "aiohttp.client.ClientSession" > /tmp/importing.log
+ bash ./.github/scripts/library_import_tests.sh "async" "AsyncZabbixAPI" "Unable to connect to" > /tmp/importing.log
- name: Raise an exception
run: |
test $(cat /tmp/importing.log | wc -l) -eq 0 || exit 1
@@ -116,7 +116,7 @@ jobs:
run: |
sudo apt-get install -y python3 python3-pip python-is-python3
pip install -r ./requirements.txt
- - name: Additional tests
+ - name: Run tests
continue-on-error: true
run: |
sleep 5
diff --git a/.github/workflows/compatibility_70.yaml b/.github/workflows/compatibility_70.yaml
index 9955570..ad14899 100644
--- a/.github/workflows/compatibility_70.yaml
+++ b/.github/workflows/compatibility_70.yaml
@@ -23,7 +23,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-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 zabbix-sender
- name: Build from sources
run: |
WORKDIR=$(pwd)
@@ -35,6 +35,26 @@ 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/
@@ -66,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/compatibility_64.yaml b/.github/workflows/compatibility_72.yaml
similarity index 66%
rename from .github/workflows/compatibility_64.yaml
rename to .github/workflows/compatibility_72.yaml
index e26b192..73ef9d3 100644
--- a/.github/workflows/compatibility_64.yaml
+++ b/.github/workflows/compatibility_72.yaml
@@ -1,5 +1,5 @@
-name: zabbix_64
-run-name: Compatibility with Zabbix 6.4 test
+name: zabbix_72
+run-name: Compatibility with Zabbix 7.2 test
on:
push:
@@ -9,10 +9,10 @@ on:
workflow_dispatch:
env:
- ZABBIX_VERSION: '6.4'
+ ZABBIX_VERSION: '7.2'
ZABBIX_BRANCH: release/$ZABBIX_VERSION
CONFIG_PATH: .github/configs/
- TEST_FILE: compatibility_api_test_6.py
+ TEST_FILE: compatibility_api_test_7.py
jobs:
compatibility:
@@ -23,7 +23,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-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 zabbix-sender
- name: Build from sources
run: |
WORKDIR=$(pwd)
@@ -35,6 +35,26 @@ 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/
@@ -66,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/compatibility_latest.yaml b/.github/workflows/compatibility_latest.yaml
index 2cc3135..7eea525 100644
--- a/.github/workflows/compatibility_latest.yaml
+++ b/.github/workflows/compatibility_latest.yaml
@@ -20,7 +20,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-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 zabbix-sender
- name: Build from sources
run: |
WORKDIR=$(pwd)
@@ -32,6 +32,26 @@ 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/
@@ -63,6 +83,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/depricated_tests.yaml b/.github/workflows/depricated_tests.yaml
new file mode 100644
index 0000000..f76ef83
--- /dev/null
+++ b/.github/workflows/depricated_tests.yaml
@@ -0,0 +1,89 @@
+name: depricated_tests
+run-name: Tests for deprecated features
+
+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: depricated_tests.py
+
+jobs:
+ depricated-tests:
+ name: Depricated tests
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+ - 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
+ - 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 --with-postgresql
+ sudo make dbschema_postgresql
+ echo -e "CacheUpdateFrequency=1\n" >> ./conf/zabbix_server.conf
+ sudo mkdir -p /etc/nginx/ssl/
+ sudo cp $WORKDIR/${{ env.CONFIG_PATH }}/.htpasswd /etc/nginx/.htpasswd
+ sudo cp $WORKDIR/${{ env.CONFIG_PATH }}/default.conf /etc/nginx/sites-enabled/default
+ sudo openssl req -x509 -nodes -days 1 -newkey rsa:2048 -keyout /etc/nginx/ssl/nginx.key -out /etc/nginx/ssl/nginx.crt -config $WORKDIR/${{ env.CONFIG_PATH }}/nginx.cnf
+ sudo chown -R www-data:www-data /etc/nginx/
+ 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 sed -i "s/Listen 80/Listen 8080/g" /etc/apache2/ports.conf
+ sudo sed -i "s///g" /etc/apache2/sites-enabled/000-default.conf
+ 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
+ - name: Start Apache & Nginx
+ run: |
+ sudo apache2ctl start
+ sudo nginx -g "daemon on; master_process on;"
+ - name: Install python3
+ run: |
+ sudo apt-get install -y python3 python3-pip python-is-python3
+ pip install -r ./requirements.txt
+ - name: Run tests
+ continue-on-error: true
+ run: |
+ sleep 5
+ python ./.github/scripts/$TEST_FILE 2>/tmp/depricated.log >/dev/null
+ - name: Send report
+ env:
+ TBOT_TOKEN: ${{ secrets.TBOT_TOKEN }}
+ TBOT_CHAT: ${{ vars.TBOT_CHAT }}
+ SUBJECT: Zabbix API depricated tests FAIL
+ run: |
+ tail -n1 /tmp/depricated.log | grep "OK" 1>/dev/null || tail /tmp/depricated.log | python ./.github/scripts/telegram_msg.py | exit 1
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index 2a26376..72e1b88 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -68,3 +68,15 @@ jobs:
tag: "v${{ env.LIBRARY_VERSION }}"
bodyFile: RELEASE_NOTES.md
artifacts: dist/*
+ - name: Send notification
+ run: |
+ python ./.github/scripts/release_notification.py
+ working-directory: ./
+ env:
+ MAIL_SERVER: ${{ secrets.MAIL_SERVER }}
+ MAIL_PORT: ${{ secrets.MAIL_PORT }}
+ MAIL_USER: ${{ secrets.MAIL_USER }}
+ MAIL_PASS: ${{ secrets.MAIL_PASS }}
+ RELEASE_RECIPIENT_LIST: ${{ secrets.RELEASE_RECIPIENT_LIST }}
+ LIBRARY_VERSION: ${{ env.LIBRARY_VERSION }}
+ REPOSITORY: ${{ github.repository }}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a068122..6fa06b8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,24 @@
+## [2.0.2](https://github.com/zabbix/python-zabbix-utils/compare/v2.0.1...v2.0.2) (2024-12-12)
+
+### Features:
+
+- added support for Zabbix 7.2
+- added support of proxy groups for Sender and AsyncSender
+
+### Changes:
+
+- discontinued support for HTTP authentication for Zabbix 7.2 and newer
+- discontinued support for Zabbix 6.4
+- added examples of deleting items
+- added examples of how to clear item history
+- added examples of how to pass get request parameters
+
+### Bug fixes:
+
+- fixed issue [#21](https://github.com/zabbix/python-zabbix-utils/issues/21) with non-obvious format of ID array passing
+- fixed issue [#26](https://github.com/zabbix/python-zabbix-utils/issues/26) with Sender and AsyncSender working with proxy groups
+- fixed small bugs and flaws
+
## [2.0.1](https://github.com/zabbix/python-zabbix-utils/compare/v2.0.0...v2.0.1) (2024-09-18)
### Features:
diff --git a/README.md b/README.md
index d891ad0..58a61c1 100644
--- a/README.md
+++ b/README.md
@@ -7,8 +7,8 @@
[![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 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_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.
@@ -29,7 +29,7 @@ Supported versions:
Tested on:
-* Zabbix 5.0, 6.0, 6.4 and 7.0
+* Zabbix 5.0, 6.0, 7.0 and 7.2
* Python 3.8, 3.9, 3.10, 3.11 and 3.12
Dependencies:
diff --git a/examples/api/asynchronous/check_auth_state.py b/examples/api/asynchronous/check_auth_state.py
index 42f8c46..cf7e3df 100644
--- a/examples/api/asynchronous/check_auth_state.py
+++ b/examples/api/asynchronous/check_auth_state.py
@@ -25,7 +25,7 @@ async def main():
# Create an instance of the AsyncZabbixAPI class
api = AsyncZabbixAPI(ZABBIX_SERVER)
- # Authenticating with Zabbix API using the provided token.
+ # Authenticating with Zabbix API using the provided username and password.
await api.login(**ZABBIX_AUTH)
# Some actions when your session can be released
diff --git a/examples/api/asynchronous/clear_history.py b/examples/api/asynchronous/clear_history.py
new file mode 100644
index 0000000..c973d6e
--- /dev/null
+++ b/examples/api/asynchronous/clear_history.py
@@ -0,0 +1,46 @@
+# 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 asyncio
+from zabbix_utils import AsyncZabbixAPI, APIRequestError
+
+# Zabbix server URL or IP address
+ZABBIX_SERVER = "127.0.0.1"
+
+# Zabbix server authentication credentials
+ZABBIX_AUTH = {
+ "user": "Admin", # Zabbix user name for authentication
+ "password": "zabbix" # Zabbix user password for authentication
+}
+
+# IDs of items for which the history should be cleared
+ITEM_IDS = [70060]
+
+
+async def main():
+ """
+ The main function to perform asynchronous tasks.
+ """
+
+ # Create an instance of the AsyncZabbixAPI class
+ api = AsyncZabbixAPI(ZABBIX_SERVER)
+
+ # Authenticating with Zabbix API using the provided username and password.
+ await api.login(**ZABBIX_AUTH)
+
+ # Clear history for items with specified IDs
+ try:
+ await api.history.clear(*ITEM_IDS)
+
+ # Alternative way to do the same (since v2.0.2):
+ # await api.history.clear(ITEM_IDS)
+ except APIRequestError as e:
+ print(f"An error occurred when attempting to delete items: {e}")
+ else:
+ # Logout to release the Zabbix API session
+ await api.logout()
+
+# Run the main coroutine
+asyncio.run(main())
diff --git a/examples/api/asynchronous/custom_client_session.py b/examples/api/asynchronous/custom_client_session.py
index 20a570b..e3bec44 100644
--- a/examples/api/asynchronous/custom_client_session.py
+++ b/examples/api/asynchronous/custom_client_session.py
@@ -34,7 +34,7 @@ async def main():
client_session=client_session
)
- # Authenticating with Zabbix API using the provided token.
+ # Authenticating with Zabbix API using the provided username and password.
await api.login(**ZABBIX_AUTH)
# Retrieve a list of hosts from the Zabbix server, including their host ID and name
diff --git a/examples/api/asynchronous/custom_ssl_context.py b/examples/api/asynchronous/custom_ssl_context.py
index 70cddfc..cd44eda 100644
--- a/examples/api/asynchronous/custom_ssl_context.py
+++ b/examples/api/asynchronous/custom_ssl_context.py
@@ -40,7 +40,7 @@ async def main():
client_session=client_session
)
- # Authenticating with Zabbix API using the provided token.
+ # Authenticating with Zabbix API using the provided username and password.
await api.login(**ZABBIX_AUTH)
# Retrieve a list of hosts from the Zabbix server, including their host ID and name
diff --git a/examples/api/asynchronous/delete_items.py b/examples/api/asynchronous/delete_items.py
new file mode 100644
index 0000000..ceca435
--- /dev/null
+++ b/examples/api/asynchronous/delete_items.py
@@ -0,0 +1,46 @@
+# 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 asyncio
+from zabbix_utils import AsyncZabbixAPI, APIRequestError
+
+# Zabbix server URL or IP address
+ZABBIX_SERVER = "127.0.0.1"
+
+# Zabbix server authentication credentials
+ZABBIX_AUTH = {
+ "user": "Admin", # Zabbix user name for authentication
+ "password": "zabbix" # Zabbix user password for authentication
+}
+
+# Item IDs to be deleted
+ITEM_IDS = [70060]
+
+
+async def main():
+ """
+ The main function to perform asynchronous tasks.
+ """
+
+ # Create an instance of the AsyncZabbixAPI class
+ api = AsyncZabbixAPI(ZABBIX_SERVER)
+
+ # Authenticating with Zabbix API using the provided username and password.
+ await api.login(**ZABBIX_AUTH)
+
+ # Delete items with specified IDs
+ try:
+ await api.item.delete(*ITEM_IDS)
+
+ # Alternative way to do the same (since v2.0.2):
+ # await api.item.delete(ITEM_IDS)
+ except APIRequestError as e:
+ print(f"An error occurred when attempting to delete items: {e}")
+ else:
+ # Logout to release the Zabbix API session
+ await api.logout()
+
+# Run the main coroutine
+asyncio.run(main())
diff --git a/examples/api/asynchronous/disabling_validate_certs.py b/examples/api/asynchronous/disabling_validate_certs.py
index 145e56e..745f55d 100644
--- a/examples/api/asynchronous/disabling_validate_certs.py
+++ b/examples/api/asynchronous/disabling_validate_certs.py
@@ -31,7 +31,7 @@ async def main():
# Note: Ignoring SSL certificate validation may expose the connection to security risks.
api = AsyncZabbixAPI(**ZABBIX_PARAMS)
- # Authenticating with Zabbix API using the provided token.
+ # Authenticating with Zabbix API using the provided username and password.
await api.login(**ZABBIX_AUTH)
# Retrieve a list of users from the Zabbix server, including their user ID and name.
diff --git a/examples/api/asynchronous/get_request_parameters.py b/examples/api/asynchronous/get_request_parameters.py
new file mode 100644
index 0000000..ad7ee6f
--- /dev/null
+++ b/examples/api/asynchronous/get_request_parameters.py
@@ -0,0 +1,51 @@
+# 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 asyncio
+from zabbix_utils import AsyncZabbixAPI
+
+# Zabbix server URL or IP address
+ZABBIX_SERVER = "127.0.0.1"
+
+# Zabbix server authentication credentials
+ZABBIX_AUTH = {
+ "user": "Admin", # Zabbix user name for authentication
+ "password": "zabbix" # Zabbix user password for authentication
+}
+
+
+async def main():
+ """
+ The main function to perform asynchronous tasks.
+ """
+
+ # Create an instance of the AsyncZabbixAPI class
+ api = AsyncZabbixAPI(ZABBIX_SERVER)
+
+ # Authenticating with Zabbix API using the provided username and password.
+ await api.login(**ZABBIX_AUTH)
+
+ # There are only three ways to pass parameters of type dictionary:
+ #
+ # 1. Specifying values directly with their keys:
+ problems = await api.problem.get(tags=[{"tag": "scope", "value": "notice", "operator": "0"}])
+ #
+ # 2. Unpacking dictionary keys and values using `**`:
+ # request_params = {"tags": [{"tag": "scope", "value": "notice", "operator": "0"}]}
+ # problems = await api.problem.get(**request_params)
+ #
+ # 3. Passing the dictionary directly as an argument (since v2.0.2):
+ # request_params = {"tags": [{"tag": "scope", "value": "notice", "operator": "0"}]}
+ # problems = await api.problem.get(request_params)
+
+ # Print the names of the retrieved users
+ for problem in problems:
+ print(problem['name'])
+
+ # Logout to release the Zabbix API session
+ await api.logout()
+
+# Run the main coroutine
+asyncio.run(main())
diff --git a/examples/api/synchronous/clear_history.py b/examples/api/synchronous/clear_history.py
new file mode 100644
index 0000000..abaa54b
--- /dev/null
+++ b/examples/api/synchronous/clear_history.py
@@ -0,0 +1,31 @@
+# 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.
+
+from zabbix_utils import ZabbixAPI, APIRequestError
+
+# Zabbix server details and authentication credentials
+ZABBIX_AUTH = {
+ "url": "127.0.0.1", # Zabbix server URL or IP address
+ "user": "Admin", # Zabbix user name for authentication
+ "password": "zabbix" # Zabbix user password for authentication
+}
+
+# IDs of items for which the history should be cleared
+ITEM_IDS = [70060]
+
+# Create an instance of the ZabbixAPI class with the specified authentication details
+api = ZabbixAPI(**ZABBIX_AUTH)
+
+# Clear history for items with specified IDs
+try:
+ api.history.clear(*ITEM_IDS)
+
+ # Alternative way to do the same (since v2.0.2):
+ # api.history.clear(*ITEM_IDS)
+except APIRequestError as e:
+ print(f"An error occurred when attempting to clear items' history: {e}")
+
+# Logout to release the Zabbix API session
+api.logout()
diff --git a/examples/api/synchronous/delete_items.py b/examples/api/synchronous/delete_items.py
new file mode 100644
index 0000000..3d77f6b
--- /dev/null
+++ b/examples/api/synchronous/delete_items.py
@@ -0,0 +1,31 @@
+# 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.
+
+from zabbix_utils import ZabbixAPI, APIRequestError
+
+# Zabbix server details and authentication credentials
+ZABBIX_AUTH = {
+ "url": "127.0.0.1", # Zabbix server URL or IP address
+ "user": "Admin", # Zabbix user name for authentication
+ "password": "zabbix" # Zabbix user password for authentication
+}
+
+# Item IDs to be deleted
+ITEM_IDS = [70060]
+
+# Create an instance of the ZabbixAPI class with the specified authentication details
+api = ZabbixAPI(**ZABBIX_AUTH)
+
+# Delete items with specified IDs
+try:
+ api.item.delete(*ITEM_IDS)
+
+ # Alternative way to do the same (since v2.0.2):
+ # api.item.delete(ITEM_IDS)
+except APIRequestError as e:
+ print(f"An error occurred when attempting to delete items: {e}")
+
+# Logout to release the Zabbix API session
+api.logout()
diff --git a/examples/api/synchronous/get_request_parameters.py b/examples/api/synchronous/get_request_parameters.py
new file mode 100644
index 0000000..ec6036c
--- /dev/null
+++ b/examples/api/synchronous/get_request_parameters.py
@@ -0,0 +1,36 @@
+# 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.
+
+from zabbix_utils import ZabbixAPI
+
+# Zabbix server details and authentication credentials
+ZABBIX_AUTH = {
+ "url": "127.0.0.1", # Zabbix server URL or IP address
+ "user": "Admin", # Zabbix user name for authentication
+ "password": "zabbix" # Zabbix user password for authentication
+}
+
+# Create an instance of the ZabbixAPI class with the specified authentication details
+api = ZabbixAPI(**ZABBIX_AUTH)
+
+# There are only three ways to pass parameters of type dictionary:
+#
+# 1. Specifying values directly with their keys:
+problems = api.problem.get(tags=[{"tag": "scope", "value": "notice", "operator": "0"}])
+#
+# 2. Unpacking dictionary keys and values using `**`:
+# request_params = {"tags": [{"tag": "scope", "value": "notice", "operator": "0"}]}
+# problems = api.problem.get(**request_params)
+#
+# 3. Passing the dictionary directly as an argument (since v2.0.2):
+# request_params = {"tags": [{"tag": "scope", "value": "notice", "operator": "0"}]}
+# problems = api.problem.get(request_params)
+
+# Print the names of the retrieved users
+for problem in problems:
+ print(problem['name'])
+
+# Logout to release the Zabbix API session
+api.logout()
diff --git a/tests/common.py b/tests/common.py
index cab5887..e8e66ec 100644
--- a/tests/common.py
+++ b/tests/common.py
@@ -81,6 +81,9 @@ class MockBasicAuth():
class MockSessionConn():
def __init__(self):
self._ssl = None
+ self.closed = False
+ def close(self):
+ self.closed = True
class MockSession():
def __init__(self, exception=None):
diff --git a/tests/test_zabbix_aioapi.py b/tests/test_zabbix_aioapi.py
index 2423d49..96f1417 100644
--- a/tests/test_zabbix_aioapi.py
+++ b/tests/test_zabbix_aioapi.py
@@ -289,8 +289,10 @@ async def test__prepare_request(self):
params={'user': DEFAULT_VALUES['user'], 'password': DEFAULT_VALUES['password']},
need_auth=True
)
- self.assertEqual(req.get('auth'), DEFAULT_VALUES['token'],
- "unexpected auth request parameter, must be: " + DEFAULT_VALUES['token'])
+ self.assertEqual(req.get('auth'), None,
+ "unexpected auth request parameter, must be: None")
+ self.assertEqual(headers.get('Authorization'), 'Bearer ' + DEFAULT_VALUES['token'],
+ "unexpected Authorization header, must be: Bearer " + DEFAULT_VALUES['token'])
self.zapi.client_session.del_auth()
await self.zapi.logout()
diff --git a/tests/test_zabbix_api.py b/tests/test_zabbix_api.py
index 49cfa43..63ec234 100644
--- a/tests/test_zabbix_api.py
+++ b/tests/test_zabbix_api.py
@@ -50,10 +50,13 @@ def mock_urlopen(*args, **kwargs):
ul,
urlopen=mock_urlopen):
- zapi = ZabbixAPI(
- http_user=DEFAULT_VALUES['user'],
- http_password=DEFAULT_VALUES['password']
- )
+ with self.assertRaises(APINotSupported,
+ msg="expected APINotSupported exception hasn't been raised"):
+ ZabbixAPI(
+ http_user=DEFAULT_VALUES['user'],
+ http_password=DEFAULT_VALUES['password']
+ )
+ zapi = ZabbixAPI()
with self.assertRaises(ProcessingError,
msg="expected ProcessingError exception hasn't been raised"):
zapi.hosts.get()
@@ -160,7 +163,9 @@ def test_login(self):
ZabbixAPI,
send_api_request=common.mock_send_sync_request):
- zapi = ZabbixAPI(http_user=DEFAULT_VALUES['user'], http_password=DEFAULT_VALUES['password'])
+ with self.assertRaises(APINotSupported, msg="expected APINotSupported exception hasn't been raised"):
+ ZabbixAPI(http_user=DEFAULT_VALUES['user'], http_password=DEFAULT_VALUES['password'])
+ zapi = ZabbixAPI()
with self.assertRaises(TypeError, msg="expected TypeError exception hasn't been raised"):
zapi = ZabbixAPI()
diff --git a/zabbix_utils/aioapi.py b/zabbix_utils/aioapi.py
index 97290ad..ccf411d 100644
--- a/zabbix_utils/aioapi.py
+++ b/zabbix_utils/aioapi.py
@@ -84,13 +84,17 @@ async def func(*args: Any, **kwargs: Any) -> Any:
# Support '_' suffix to avoid conflicts with python keywords
method = removesuffix(self.object, '_') + "." + removesuffix(name, '_')
+ # Support passing list of ids and params as a dict
+ params = kwargs or (
+ (args[0] if type(args[0]) in (list, dict,) else list(args)) if args else None)
+
log.debug("Executing %s method", method)
need_auth = method not in ModuleUtils.UNAUTH_METHODS
response = await self.parent.send_async_request(
method,
- args or kwargs,
+ params,
need_auth
)
return response.get('result')
@@ -133,6 +137,7 @@ def __init__(self, url: Optional[str] = None,
client_params["connector"] = aiohttp.TCPConnector(
ssl=self.validate_certs
)
+ # HTTP Auth unsupported since Zabbix 7.2
if http_user and http_password:
client_params["auth"] = aiohttp.BasicAuth(
login=http_user,
@@ -149,6 +154,10 @@ def __init__(self, url: Optional[str] = None,
self.__check_version(skip_version_check)
+ if self.version > 7.0 and http_user and http_password:
+ self.__close_session()
+ raise APINotSupported("HTTP authentication unsupported since Zabbix 7.2.")
+
def __getattr__(self, name: str) -> Callable:
"""Dynamic creation of an API object.
@@ -167,14 +176,18 @@ async def __aenter__(self) -> Callable:
async def __aexit__(self, *args) -> None:
await self.logout()
- async def __close_session(self) -> None:
+ async def __aclose_session(self) -> None:
if self.__internal_client:
await self.__internal_client.close()
async def __exception(self, exc) -> None:
- await self.__close_session()
+ await self.__aclose_session()
raise exc from exc
+ def __close_session(self) -> None:
+ if self.__internal_client:
+ self.__internal_client._connector.close()
+
def api_version(self) -> APIVersion:
"""Return object of Zabbix API version.
@@ -257,13 +270,13 @@ async def logout(self) -> None:
if self.__use_token:
self.__session_id = None
self.__use_token = False
- await self.__close_session()
+ await self.__aclose_session()
return
log.debug("Logout from Zabbix API")
await self.user.logout()
self.__session_id = None
- await self.__close_session()
+ await self.__aclose_session()
else:
log.debug("You're not logged in Zabbix API")
@@ -305,7 +318,9 @@ def __prepare_request(self, method: str, params: Optional[dict] = None,
if need_auth:
if not self.__session_id:
raise ProcessingError("You're not logged in Zabbix API")
- if self.version < 6.4 or self.client_session._default_auth is not None:
+ if self.version < 6.4:
+ request['auth'] = self.__session_id
+ elif self.version <= 7.0 and self.client_session._default_auth is not None:
request['auth'] = self.__session_id
else:
headers["Authorization"] = f"Bearer {self.__session_id}"
@@ -401,6 +416,7 @@ def send_sync_request(self, method: str, params: Optional[dict] = None,
request_json, headers = self.__prepare_request(method, params, need_auth)
+ # HTTP Auth unsupported since Zabbix 7.2
basic_auth = self.client_session._default_auth
if basic_auth is not None:
headers["Authorization"] = "Basic " + base64.b64encode(
@@ -429,9 +445,14 @@ def send_sync_request(self, method: str, params: Optional[dict] = None,
resp = ul.urlopen(req, context=ctx)
resp_json = json.loads(resp.read().decode('utf-8'))
except URLError as err:
+ self.__close_session()
raise ProcessingError(f"Unable to connect to {self.url}:", err) from None
except ValueError as err:
+ self.__close_session()
raise ProcessingError("Unable to parse json:", err) from None
+ except Exception as err:
+ self.__close_session()
+ raise ProcessingError(err) from None
return self.__check_response(method, resp_json)
diff --git a/zabbix_utils/aiosender.py b/zabbix_utils/aiosender.py
index 7748c56..6e51d4e 100644
--- a/zabbix_utils/aiosender.py
+++ b/zabbix_utils/aiosender.py
@@ -29,12 +29,12 @@
import logging
import configparser
-from typing import Callable, Union, Optional
+from typing import Callable, Union, Optional, Tuple
from .logger import EmptyHandler
from .common import ZabbixProtocol
from .exceptions import ProcessingError
-from .types import TrapperResponse, ItemValue, Cluster
+from .types import TrapperResponse, ItemValue, Cluster, Node
log = logging.getLogger(__name__)
log.addHandler(EmptyHandler())
@@ -138,99 +138,115 @@ def __create_request(self, items: list) -> dict:
"data": [i.to_json() for i in items]
}
- async def __chunk_send(self, items: list) -> dict:
- responses = {}
-
- packet = ZabbixProtocol.create_packet(self.__create_request(items), log, self.compression)
-
- for cluster in self.clusters:
- active_node = None
+ async def __send_to_cluster(self, cluster: Cluster, packet: bytes) -> Optional[Tuple[Node, dict]]:
+ active_node = None
+ active_node_idx = 0
+ for i, node in enumerate(cluster.nodes):
- for i, node in enumerate(cluster.nodes):
+ log.debug('Trying to send data to %s', node)
- log.debug('Trying to send data to %s', node)
+ connection_params = {
+ "host": node.address,
+ "port": node.port
+ }
- connection_params = {
- "host": node.address,
- "port": node.port
- }
+ if self.source_ip:
+ connection_params['local_addr'] = (self.source_ip, 0)
- if self.source_ip:
- connection_params['local_addr'] = (self.source_ip, 0)
+ if self.ssl_context is not None:
+ connection_params['ssl'] = self.ssl_context(self.tls)
+ if not isinstance(connection_params['ssl'], ssl.SSLContext):
+ raise TypeError(
+ 'Function "ssl_context" must return "ssl.SSLContext".') from None
- if self.ssl_context is not None:
- connection_params['ssl'] = self.ssl_context(self.tls)
- if not isinstance(connection_params['ssl'], ssl.SSLContext):
- raise TypeError(
- 'Function "ssl_context" must return "ssl.SSLContext".') from None
-
- connection = asyncio.open_connection(**connection_params)
-
- try:
- reader, writer = await asyncio.wait_for(connection, timeout=self.timeout)
- except asyncio.TimeoutError:
- log.debug(
- 'The connection to %s timed out after %d seconds',
- node,
- self.timeout
- )
- except (ConnectionRefusedError, socket.gaierror) as err:
- log.debug(
- 'An error occurred while trying to connect to %s: %s',
- node,
- getattr(err, 'msg', str(err))
- )
- else:
- if i > 0:
- cluster.nodes[0], cluster.nodes[i] = cluster.nodes[i], cluster.nodes[0]
- active_node = node
- break
-
- if active_node is None:
- log.error(
- 'Couldn\'t connect to all of cluster nodes: %s',
- str(list(cluster.nodes))
- )
- raise ProcessingError(
- f"Couldn't connect to all of cluster nodes: {list(cluster.nodes)}"
- )
+ connection = asyncio.open_connection(**connection_params)
try:
- writer.write(packet)
- send_data = writer.drain()
- await asyncio.wait_for(send_data, timeout=self.timeout)
- except (asyncio.TimeoutError, socket.timeout) as err:
- log.error(
- 'The connection to %s timed out after %d seconds while trying to send',
- active_node,
+ reader, writer = await asyncio.wait_for(connection, timeout=self.timeout)
+ except asyncio.TimeoutError:
+ log.debug(
+ 'The connection to %s timed out after %d seconds',
+ node,
self.timeout
)
- writer.close()
- await writer.wait_closed()
- raise err
- except (OSError, socket.error) as err:
- log.warning(
- 'An error occurred while trying to send to %s: %s',
- active_node,
+ except (ConnectionRefusedError, socket.gaierror) as err:
+ log.debug(
+ 'An error occurred while trying to connect to %s: %s',
+ node,
getattr(err, 'msg', str(err))
)
- writer.close()
- await writer.wait_closed()
- raise err
- try:
- response = await self.__get_response(reader)
- except (ConnectionResetError, asyncio.exceptions.IncompleteReadError) as err:
- log.debug('Get value error: %s', err)
- raise err
- log.debug('Response from %s: %s', active_node, response)
+ else:
+ active_node_idx = i
+ if i > 0:
+ cluster.nodes[0], cluster.nodes[i] = cluster.nodes[i], cluster.nodes[0]
+ active_node_idx = 0
+ active_node = node
+ break
+
+ if active_node is None:
+ log.error(
+ 'Couldn\'t connect to all of cluster nodes: %s',
+ str(list(cluster.nodes))
+ )
+ raise ProcessingError(
+ f"Couldn't connect to all of cluster nodes: {list(cluster.nodes)}"
+ )
- if response and response.get('response') != 'success':
+ try:
+ writer.write(packet)
+ send_data = writer.drain()
+ await asyncio.wait_for(send_data, timeout=self.timeout)
+ except (asyncio.TimeoutError, socket.timeout) as err:
+ log.error(
+ 'The connection to %s timed out after %d seconds while trying to send',
+ active_node,
+ self.timeout
+ )
+ writer.close()
+ await writer.wait_closed()
+ raise err
+ except (OSError, socket.error) as err:
+ log.warning(
+ 'An error occurred while trying to send to %s: %s',
+ active_node,
+ getattr(err, 'msg', str(err))
+ )
+ writer.close()
+ await writer.wait_closed()
+ raise err
+ try:
+ response = await self.__get_response(reader)
+ except (ConnectionResetError, asyncio.exceptions.IncompleteReadError) as err:
+ log.debug('Get value error: %s', err)
+ raise err
+ log.debug('Response from %s: %s', active_node, response)
+
+ if response and response.get('response') != 'success':
+ if response.get('redirect'):
+ log.debug(
+ 'Packet was redirected from %s to %s. Proxy group revision: %s.',
+ active_node,
+ response['redirect']['address'],
+ response['redirect']['revision']
+ )
+ cluster.nodes[active_node_idx] = Node(*response['redirect']['address'].split(':'))
+ active_node, response = await self.__send_to_cluster(cluster, packet)
+ else:
raise ProcessingError(response) from None
- responses[active_node] = response
+ writer.close()
+ await writer.wait_closed()
- writer.close()
- await writer.wait_closed()
+ return active_node, response
+
+ async def __chunk_send(self, items: list) -> dict:
+ responses = {}
+
+ packet = ZabbixProtocol.create_packet(self.__create_request(items), log, self.compression)
+
+ for cluster in self.clusters:
+ active_node, response = await self.__send_to_cluster(cluster, packet)
+ responses[active_node] = response
return responses
diff --git a/zabbix_utils/api.py b/zabbix_utils/api.py
index 1039757..c3ae9ad 100644
--- a/zabbix_utils/api.py
+++ b/zabbix_utils/api.py
@@ -83,13 +83,17 @@ def func(*args: Any, **kwargs: Any) -> Any:
# Support '_' suffix to avoid conflicts with python keywords
method = removesuffix(self.object, '_') + "." + removesuffix(name, '_')
+ # Support passing list of ids and params as a dict
+ params = kwargs or (
+ (args[0] if type(args[0]) in (list, dict,) else list(args)) if args else None)
+
log.debug("Executing %s method", method)
need_auth = method not in ModuleUtils.UNAUTH_METHODS
return self.parent.send_api_request(
method,
- args or kwargs,
+ params,
need_auth
).get('result')
@@ -131,16 +135,21 @@ def __init__(self, url: Optional[str] = None, token: Optional[str] = None,
self.validate_certs = validate_certs
self.timeout = timeout
+ # HTTP Auth unsupported since Zabbix 7.2
if http_user and http_password:
self.__basic_auth(http_user, http_password)
if ssl_context is not None:
if not isinstance(ssl_context, ssl.SSLContext):
- raise TypeError('Function "ssl_context" must return "ssl.SSLContext".') from None
+ raise TypeError(
+ 'Parameter "ssl_context" must be an "ssl.SSLContext".') from None
self.ssl_context = ssl_context
self.__check_version(skip_version_check)
+ if self.version > 7.0 and http_user and http_password:
+ raise APINotSupported("HTTP authentication unsupported since Zabbix 7.2.")
+
if token or user or password:
self.login(token, user, password)
@@ -316,7 +325,9 @@ def send_api_request(self, method: str, params: Optional[dict] = None,
if need_auth:
if not self.__session_id:
raise ProcessingError("You're not logged in Zabbix API")
- if self.version < 6.4 or self.__basic_cred is not None:
+ if self.version < 6.4:
+ request_json['auth'] = self.__session_id
+ elif self.version <= 7.0 and self.__basic_cred is not None:
request_json['auth'] = self.__session_id
else:
headers["Authorization"] = f"Bearer {self.__session_id}"
diff --git a/zabbix_utils/sender.py b/zabbix_utils/sender.py
index ec21b6f..cb34ca8 100644
--- a/zabbix_utils/sender.py
+++ b/zabbix_utils/sender.py
@@ -27,12 +27,12 @@
import logging
import configparser
-from typing import Callable, Optional, Union
+from typing import Callable, Optional, Union, Tuple
from .logger import EmptyHandler
from .common import ZabbixProtocol
from .exceptions import ProcessingError
-from .types import TrapperResponse, ItemValue, Cluster
+from .types import TrapperResponse, ItemValue, Cluster, Node
log = logging.getLogger(__name__)
log.addHandler(EmptyHandler())
@@ -116,7 +116,7 @@ def __load_config(self, filepath: str) -> None:
config.read_string('[root]\n' + cfg.read())
self.__read_config(config['root'])
- def __get_response(self, conn: socket) -> Optional[str]:
+ def __get_response(self, conn: socket) -> Optional[dict]:
try:
result = json.loads(
ZabbixProtocol.parse_sync_packet(conn, log, ProcessingError)
@@ -135,99 +135,116 @@ def __create_request(self, items: list) -> dict:
"data": [i.to_json() for i in items]
}
- def __chunk_send(self, items: list) -> dict:
- responses = {}
-
- packet = ZabbixProtocol.create_packet(self.__create_request(items), log, self.compression)
-
- for cluster in self.clusters:
- active_node = None
+ def __send_to_cluster(self, cluster: Cluster, packet: bytes) -> Optional[Tuple[Node, dict]]:
+ active_node = None
+ active_node_idx = 0
+ for i, node in enumerate(cluster.nodes):
- for i, node in enumerate(cluster.nodes):
+ log.debug('Trying to send data to %s', node)
- log.debug('Trying to send data to %s', node)
-
- try:
- if self.use_ipv6:
- connection = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
- else:
- connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- except socket.error:
- raise ProcessingError(f"Error creating socket for {node}") from None
-
- connection.settimeout(self.timeout)
-
- if self.source_ip:
- connection.bind((self.source_ip, 0,))
-
- try:
- connection.connect((node.address, node.port))
- except (TimeoutError, socket.timeout):
- log.debug(
- 'The connection to %s timed out after %d seconds',
- node,
- self.timeout
- )
- except (ConnectionRefusedError, socket.gaierror) as err:
- log.debug(
- 'An error occurred while trying to connect to %s: %s',
- node,
- getattr(err, 'msg', str(err))
- )
+ try:
+ if self.use_ipv6:
+ connection = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
else:
- if i > 0:
- cluster.nodes[0], cluster.nodes[i] = cluster.nodes[i], cluster.nodes[0]
- active_node = node
- break
-
- if active_node is None:
- log.error(
- 'Couldn\'t connect to all of cluster nodes: %s',
- str(list(cluster.nodes))
- )
- connection.close()
- raise ProcessingError(
- f"Couldn't connect to all of cluster nodes: {list(cluster.nodes)}"
- )
+ connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ except socket.error:
+ raise ProcessingError(f"Error creating socket for {node}") from None
+
+ connection.settimeout(self.timeout)
- if self.socket_wrapper is not None:
- connection = self.socket_wrapper(connection, self.tls)
+ if self.source_ip:
+ connection.bind((self.source_ip, 0,))
try:
- connection.sendall(packet)
- except (TimeoutError, socket.timeout) as err:
- log.error(
- 'The connection to %s timed out after %d seconds while trying to send',
- active_node,
+ connection.connect((node.address, node.port))
+ except (TimeoutError, socket.timeout):
+ log.debug(
+ 'The connection to %s timed out after %d seconds',
+ node,
self.timeout
)
- connection.close()
- raise err
- except (OSError, socket.error) as err:
- log.warning(
- 'An error occurred while trying to send to %s: %s',
- active_node,
+ except (ConnectionRefusedError, socket.gaierror) as err:
+ log.debug(
+ 'An error occurred while trying to connect to %s: %s',
+ node,
getattr(err, 'msg', str(err))
)
- connection.close()
- raise err
+ else:
+ active_node_idx = i
+ if i > 0:
+ cluster.nodes[0], cluster.nodes[i] = cluster.nodes[i], cluster.nodes[0]
+ active_node_idx = 0
+ active_node = node
+ break
+
+ if active_node is None:
+ log.error(
+ 'Couldn\'t connect to all of cluster nodes: %s',
+ str(list(cluster.nodes))
+ )
+ connection.close()
+ raise ProcessingError(
+ f"Couldn't connect to all of cluster nodes: {list(cluster.nodes)}"
+ )
- try:
- response = self.__get_response(connection)
- except ConnectionResetError as err:
- log.debug('Get value error: %s', err)
- raise err
- log.debug('Response from %s: %s', active_node, response)
+ if self.socket_wrapper is not None:
+ connection = self.socket_wrapper(connection, self.tls)
- if response and response.get('response') != 'success':
+ try:
+ connection.sendall(packet)
+ except (TimeoutError, socket.timeout) as err:
+ log.error(
+ 'The connection to %s timed out after %d seconds while trying to send',
+ active_node,
+ self.timeout
+ )
+ connection.close()
+ raise err
+ except (OSError, socket.error) as err:
+ log.warning(
+ 'An error occurred while trying to send to %s: %s',
+ active_node,
+ getattr(err, 'msg', str(err))
+ )
+ connection.close()
+ raise err
+
+ try:
+ response = self.__get_response(connection)
+ except ConnectionResetError as err:
+ log.debug('Get value error: %s', err)
+ raise err
+ log.debug('Response from %s: %s', active_node, response)
+
+ 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,
+ response['redirect']['address'],
+ response['redirect']['revision']
+ )
+ cluster.nodes[active_node_idx] = Node(*response['redirect']['address'].split(':'))
+ active_node, response = self.__send_to_cluster(cluster, packet)
+ else:
raise socket.error(response)
- responses[active_node] = response
+ try:
+ connection.close()
+ except socket.error:
+ pass
- try:
- connection.close()
- except socket.error:
- pass
+ return active_node, response
+
+ def __chunk_send(self, items: list) -> dict:
+ responses = {}
+
+ packet = ZabbixProtocol.create_packet(self.__create_request(items), log, self.compression)
+
+ for cluster in self.clusters:
+ active_node, response = self.__send_to_cluster(cluster, packet)
+ responses[active_node] = response
return responses
diff --git a/zabbix_utils/types.py b/zabbix_utils/types.py
index 2a4274f..60dc6a5 100644
--- a/zabbix_utils/types.py
+++ b/zabbix_utils/types.py
@@ -349,7 +349,7 @@ class Cluster():
"""
def __init__(self, addr: list):
- self.__nodes = self.__parse_ha_node(addr)
+ self.nodes = self.__parse_ha_node(addr)
def __parse_ha_node(self, node_list: list) -> list:
nodes = []
@@ -363,21 +363,11 @@ def __parse_ha_node(self, node_list: list) -> list:
return nodes
def __str__(self) -> str:
- return json.dumps([(node.address, node.port) for node in self.__nodes])
+ return json.dumps([(node.address, node.port) for node in self.nodes])
def __repr__(self) -> str:
return self.__str__()
- @property
- def nodes(self) -> list:
- """Returns list of Node objects.
-
- Returns:
- list: List of Node objects
- """
-
- return self.__nodes
-
class AgentResponse:
"""Contains response from Zabbix agent/agent2.
diff --git a/zabbix_utils/version.py b/zabbix_utils/version.py
index 7a3edd3..15c3e95 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.1"
+__version__ = "2.0.2"
__min_supported__ = 5.0
-__max_supported__ = 7.0
+__max_supported__ = 7.2