Fixed hostgroup generation issues, added proxygroup support.

This commit is contained in:
TheNetworkGuy 2024-06-12 13:42:21 +02:00
parent 6f044cb228
commit d1e864c75b
2 changed files with 87 additions and 67 deletions

View File

@ -5,8 +5,7 @@ Device specific handeling for Netbox to Zabbix
""" """
from os import sys from os import sys
from logging import getLogger from logging import getLogger
from pyzabbix import ZabbixAPIException from zabbix_utils import APIRequestError
from packaging import version
from modules.exceptions import (SyncInventoryError, TemplateError, SyncExternalError, from modules.exceptions import (SyncInventoryError, TemplateError, SyncExternalError,
InterfaceConfigError, JournalError) InterfaceConfigError, JournalError)
from modules.interface import ZabbixInterface from modules.interface import ZabbixInterface
@ -314,10 +313,7 @@ class NetworkDevice():
e = f"Found group {group['name']} for host {self.name}." e = f"Found group {group['name']} for host {self.name}."
self.logger.debug(e) self.logger.debug(e)
return True return True
e = (f"Unable to find group '{self.hostgroup}' " return False
f"for host {self.name} in Zabbix.")
self.logger.warning(e)
raise SyncInventoryError(e)
def cleanup(self): def cleanup(self):
""" """
@ -332,7 +328,7 @@ class NetworkDevice():
e = f"Deleted host {self.name} from Zabbix." e = f"Deleted host {self.name} from Zabbix."
self.logger.info(e) self.logger.info(e)
self.create_journal_entry("warning", "Deleted host from Zabbix") self.create_journal_entry("warning", "Deleted host from Zabbix")
except ZabbixAPIException as e: except APIRequestError as e:
e = f"Zabbix returned the following error: {str(e)}." e = f"Zabbix returned the following error: {str(e)}."
self.logger.error(e) self.logger.error(e)
raise SyncExternalError(e) from e raise SyncExternalError(e) from e
@ -371,23 +367,6 @@ class NetworkDevice():
self.logger.warning(e) self.logger.warning(e)
raise SyncInventoryError(e) from e raise SyncInventoryError(e) from e
# def setProxy(self, proxy_list):
# """ check if Zabbix Proxy has been defined in config context """
# if "zabbix" in self.nb.config_context:
# if "proxy" in self.nb.config_context["zabbix"]:
# proxy = self.nb.config_context["zabbix"]["proxy"]
# # Try matching proxy
# for px in proxy_list:
# if px["name"] == proxy:
# self.zbxproxy = px["proxyid"]
# self.logger.debug(f"Found proxy {proxy}"
# f" for {self.name}.")
# return True
# e = f"{self.name}: Defined proxy {proxy} not found."
# self.logger.warning(e)
# return False
return True
def setProxy(self, proxy_list): def setProxy(self, proxy_list):
""" """
Sets proxy or proxy group if this Sets proxy or proxy group if this
@ -400,7 +379,12 @@ class NetworkDevice():
return False return False
# Proxy group takes priority over a proxy due # Proxy group takes priority over a proxy due
# to it being HA and therefore being more reliable # to it being HA and therefore being more reliable
for proxy_type in ('proxy_group', 'proxy'): # Includes proxy group fix since Zabbix <= 6 should ignore this
proxy_types = ["proxy"]
if str(self.zabbix.version).startswith('7'):
# Only insert groups in front of list for Zabbix7
proxy_types.insert(0, "proxy_group")
for proxy_type in proxy_types:
# Check if the key exists in Netbox CC # Check if the key exists in Netbox CC
if proxy_type in self.nb.config_context["zabbix"]: if proxy_type in self.nb.config_context["zabbix"]:
proxy_name = self.nb.config_context["zabbix"][proxy_type] proxy_name = self.nb.config_context["zabbix"][proxy_type]
@ -413,6 +397,9 @@ class NetworkDevice():
if proxy["name"] == proxy_name: if proxy["name"] == proxy_name:
self.zbxproxy = proxy self.zbxproxy = proxy
return True return True
else:
self.logger.warning(f"Device {self.name}: unable to find proxy {proxy_name}")
break
return False return False
def createInZabbix(self, groups, templates, proxies, def createInZabbix(self, groups, templates, proxies,
@ -424,7 +411,10 @@ class NetworkDevice():
if not self._zabbixHostnameExists(): if not self._zabbixHostnameExists():
# Get group and template ID's for host # Get group and template ID's for host
if not self.getZabbixGroup(groups): if not self.getZabbixGroup(groups):
raise SyncInventoryError() e = (f"Unable to find group '{self.hostgroup}' "
f"for host {self.name} in Zabbix.")
self.logger.warning(e)
raise SyncInventoryError(e)
self.zbxTemplatePrepper(templates) self.zbxTemplatePrepper(templates)
templateids = [] templateids = []
for template in self.zbx_templates: for template in self.zbx_templates:
@ -449,7 +439,7 @@ class NetworkDevice():
if self.zbxproxy: if self.zbxproxy:
# If a lower version than 7 is used, we can assume that # If a lower version than 7 is used, we can assume that
# the proxy is a normal proxy and not a proxy group # the proxy is a normal proxy and not a proxy group
if version.parse(self.zabbix.version) < version.parse("7.0.0"): if not str(self.zabbix.version).startswith('7'):
create_data["proxy_hostid"] = self.zbxproxy["id"] create_data["proxy_hostid"] = self.zbxproxy["id"]
else: else:
# Configure either a proxy or proxy group # Configure either a proxy or proxy group
@ -459,7 +449,7 @@ class NetworkDevice():
try: try:
host = self.zabbix.host.create(**create_data) host = self.zabbix.host.create(**create_data)
self.zabbix_id = host["hostids"][0] self.zabbix_id = host["hostids"][0]
except ZabbixAPIException as e: except APIRequestError as e:
e = f"Couldn't add {self.name}, Zabbix returned {str(e)}." e = f"Couldn't add {self.name}, Zabbix returned {str(e)}."
self.logger.error(e) self.logger.error(e)
raise SyncExternalError(e) from e raise SyncExternalError(e) from e
@ -483,7 +473,7 @@ class NetworkDevice():
self.logger.info(e) self.logger.info(e)
data = {'groupid': groupid["groupids"][0], 'name': self.hostgroup} data = {'groupid': groupid["groupids"][0], 'name': self.hostgroup}
return data return data
except ZabbixAPIException as e: except APIRequestError as e:
e = f"Couldn't add hostgroup, Zabbix returned {str(e)}." e = f"Couldn't add hostgroup, Zabbix returned {str(e)}."
self.logger.error(e) self.logger.error(e)
raise SyncExternalError(e) from e raise SyncExternalError(e) from e
@ -495,19 +485,30 @@ class NetworkDevice():
""" """
try: try:
self.zabbix.host.update(hostid=self.zabbix_id, **kwargs) self.zabbix.host.update(hostid=self.zabbix_id, **kwargs)
except ZabbixAPIException as e: except APIRequestError as e:
e = f"Zabbix returned the following error: {str(e)}." e = f"Zabbix returned the following error: {str(e)}."
self.logger.error(e) self.logger.error(e)
raise SyncExternalError(e) from e raise SyncExternalError(e) from None
self.logger.info(f"Updated host {self.name} with data {kwargs}.") self.logger.info(f"Updated host {self.name} with data {kwargs}.")
self.create_journal_entry("info", "Updated host in Zabbix with latest NB data.") self.create_journal_entry("info", "Updated host in Zabbix with latest NB data.")
def ConsistencyCheck(self, groups, templates, proxies, proxy_power): def ConsistencyCheck(self, groups, templates, proxies, proxy_power, create_hostgroups):
# pylint: disable=too-many-branches, too-many-statements # pylint: disable=too-many-branches, too-many-statements
""" """
Checks if Zabbix object is still valid with Netbox parameters. Checks if Zabbix object is still valid with Netbox parameters.
""" """
self.getZabbixGroup(groups) # Check if the hostgroup exists.
# If not, create the hostgroup and try finding the group again
if not self.getZabbixGroup(groups):
if create_hostgroups:
new_group = self.createZabbixHostgroup()
groups.append(new_group)
self.getZabbixGroup(groups)
else:
e = (f"Device {self.name}: different hostgroup is required but "
"unable to create hostgroup without generation permission.")
self.logger.warning(e)
raise SyncInventoryError(e)
self.zbxTemplatePrepper(templates) self.zbxTemplatePrepper(templates)
self.setProxy(proxies) self.setProxy(proxies)
host = self.zabbix.host.get(filter={'hostid': self.zabbix_id}, host = self.zabbix.host.get(filter={'hostid': self.zabbix_id},
@ -566,39 +567,56 @@ class NetworkDevice():
else: else:
self.logger.warning(f"Device {self.name}: status OUT of sync.") self.logger.warning(f"Device {self.name}: status OUT of sync.")
self.updateZabbixHost(status=str(self.zabbix_state)) self.updateZabbixHost(status=str(self.zabbix_state))
### CHANGE THIS
# Check if a proxy has been defined # Check if a proxy has been defined
if self.zbxproxy: if self.zbxproxy:
# Check if expected proxyID matches with configured proxy # Check if proxy or proxy group is defined
if (("proxy_hostid" in host and host["proxy_hostid"] == self.zbxproxy) or if (self.zbxproxy["idtype"] in host and
("proxyid" in host and host["proxyid"] == self.zbxproxy)): host[self.zbxproxy["idtype"]] == self.zbxproxy["id"]):
self.logger.debug(f"Device {self.name}: proxy in-sync.") self.logger.debug(f"Device {self.name}: proxy in-sync.")
# Backwards compatibility for Zabbix <= 6
elif "proxy_hostid" in host and host["proxy_hostid"] == self.zbxproxy["id"]:
self.logger.debug(f"Device {self.name}: proxy in-sync.")
# Proxy does not match, update Zabbix
else: else:
# Proxy diff, update value
self.logger.warning(f"Device {self.name}: proxy OUT of sync.") self.logger.warning(f"Device {self.name}: proxy OUT of sync.")
if version.parse(self.zabbix.api_version()) < version.parse("7.0.0"): # Zabbix <= 6 patch
self.updateZabbixHost(proxy_hostid=self.zbxproxy) if not str(self.zabbix.version).startswith('7'):
self.updateZabbixHost(proxy_hostid=self.zbxproxy['id'])
# Zabbix 7+
else: else:
self.updateZabbixHost(proxyid=self.zbxproxy) # Prepare data structure for updating either proxy or group
update_data = {self.zbxproxy["idtype"]: self.zbxproxy["id"],
"monitored_by": self.zbxproxy['monitored_by']}
self.updateZabbixHost(**update_data)
else: else:
if (("proxy_hostid" in host and not host["proxy_hostid"] == "0") # No proxy is defined in Netbox
or ("proxyid" in host and not host["proxyid"] == "0")): proxy_set = False
if proxy_power: # Check if a proxy is defined. Uses the proxy_hostid key for backwards compatibility
# Variable full_proxy_sync has been enabled for key in ("proxy_hostid", "proxyid", "proxy_groupid"):
# delete the proxy link in Zabbix if key in host:
if version.parse(self.zabbix.api_version()) < version.parse("7.0.0"): if bool(int(host[key])):
self.updateZabbixHost(proxy_hostid=self.zbxproxy) proxy_set = True
else: if proxy_power and proxy_set:
self.updateZabbixHost(proxyid=self.zbxproxy) # Zabbix <= 6 fix
else: self.logger.warning(f"Device {self.name}: no proxy is configured in Netbox "
# Instead of deleting the proxy config in zabbix and "but is configured in Zabbix. Removing proxy config in Zabbix")
# forcing potential data loss, if "proxy_hostid" in host and bool(host["proxy_hostid"]):
# an error message is displayed. self.updateZabbixHost(proxy_hostid=0)
self.logger.error(f"Device {self.name} is configured " # Zabbix 7 proxy
f"with proxy in Zabbix but not in Netbox. The" elif "proxyid" in host and bool(host["proxyid"]):
" -p flag was ommited: no " self.updateZabbixHost(proxyid=0, monitored_by=0)
"changes have been made.") # Zabbix 7 proxy group
elif "proxy_groupid" in host and bool(host["proxy_groupid"]):
self.updateZabbixHost(proxy_groupid=0, monitored_by=0)
# Checks if a proxy has been defined in Zabbix and if proxy_power config has been set
if proxy_set and not proxy_power:
# Display error message
self.logger.error(f"Device {self.name} is configured "
f"with proxy in Zabbix but not in Netbox. The"
" -p flag was ommited: no "
"changes have been made.")
if not proxy_set:
self.logger.debug(f"Device {self.name}: proxy in-sync.")
# Check host inventory # Check host inventory
if inventory_sync: if inventory_sync:
# check inventory mode first, as we need it set to parse # check inventory mode first, as we need it set to parse
@ -665,7 +683,7 @@ class NetworkDevice():
e = f"Solved {self.name} interface conflict." e = f"Solved {self.name} interface conflict."
self.logger.info(e) self.logger.info(e)
self.create_journal_entry("info", e) self.create_journal_entry("info", e)
except ZabbixAPIException as e: except APIRequestError as e:
e = f"Zabbix returned the following error: {str(e)}." e = f"Zabbix returned the following error: {str(e)}."
self.logger.error(e) self.logger.error(e)
raise SyncExternalError(e) from e raise SyncExternalError(e) from e

View File

@ -5,10 +5,8 @@
import logging import logging
import argparse import argparse
from os import environ, path, sys from os import environ, path, sys
from packaging import version
from pynetbox import api from pynetbox import api
#from pyzabbix import ZabbixAPI, ZabbixAPIException from zabbix_utils import ZabbixAPI, APIRequestError
from zabbix_utils import ZabbixAPI, APIRequestError, ProcessingError
from modules.device import NetworkDevice from modules.device import NetworkDevice
from modules.tools import convert_recordset, proxy_prepper from modules.tools import convert_recordset, proxy_prepper
from modules.exceptions import EnvironmentVarError, HostgroupError, SyncError from modules.exceptions import EnvironmentVarError, HostgroupError, SyncError
@ -92,7 +90,7 @@ def main(arguments):
raise HostgroupError(e) raise HostgroupError(e)
# Set Zabbix API # Set Zabbix API
try: try:
zabbix = ZabbixAPI(zabbix_host) zabbix = ZabbixAPI(zabbix_host, password=zabbix_pass)
if "ZABBIX_TOKEN" in env_vars: if "ZABBIX_TOKEN" in env_vars:
zabbix.login(token=zabbix_token) zabbix.login(token=zabbix_token)
else: else:
@ -104,7 +102,7 @@ def main(arguments):
e = f"Zabbix returned the following error: {str(e)}." e = f"Zabbix returned the following error: {str(e)}."
logger.error(e) logger.error(e)
# Set API parameter mapping based on API version # Set API parameter mapping based on API version
if version.parse(zabbix.version) < version.parse("7.0.0"): if not str(zabbix.version).startswith('7'):
proxy_name = "host" proxy_name = "host"
else: else:
proxy_name = "name" proxy_name = "name"
@ -116,7 +114,10 @@ def main(arguments):
zabbix_groups = zabbix.hostgroup.get(output=['groupid', 'name']) zabbix_groups = zabbix.hostgroup.get(output=['groupid', 'name'])
zabbix_templates = zabbix.template.get(output=['templateid', 'name']) zabbix_templates = zabbix.template.get(output=['templateid', 'name'])
zabbix_proxies = zabbix.proxy.get(output=['proxyid', proxy_name]) zabbix_proxies = zabbix.proxy.get(output=['proxyid', proxy_name])
zabbix_proxygroups = zabbix.proxygroup.get(output=["proxy_groupid", "name"]) # Set empty list for proxy processing Zabbix <= 6
zabbix_proxygroups = []
if str(zabbix.version).startswith('7'):
zabbix_proxygroups = zabbix.proxygroup.get(output=["proxy_groupid", "name"])
# Sanitize proxy data # Sanitize proxy data
if proxy_name == "host": if proxy_name == "host":
for proxy in zabbix_proxies: for proxy in zabbix_proxies:
@ -170,7 +171,8 @@ def main(arguments):
# Check if device is already in Zabbix # Check if device is already in Zabbix
if device.zabbix_id: if device.zabbix_id:
device.ConsistencyCheck(zabbix_groups, zabbix_templates, device.ConsistencyCheck(zabbix_groups, zabbix_templates,
zabbix_proxy_list, full_proxy_sync) zabbix_proxy_list, full_proxy_sync,
create_hostgroups)
continue continue
# Add hostgroup is config is set # Add hostgroup is config is set
# and Hostgroup is not present in Zabbix # and Hostgroup is not present in Zabbix