First working version of clustered IP monitoring

This commit is contained in:
Raymond Kuiper 2024-07-26 15:52:02 +02:00
parent a0ea21d731
commit f13b86e2e0
4 changed files with 76 additions and 15 deletions

View File

@ -70,18 +70,26 @@ ZABBIX_TOKEN=othersecrettoken
### Netbox custom fields
Use the following custom fields in Netbox (if you are using config context for the template information then the zabbix_template field is not required):
```
* Type: Integer
* Name: zabbix_hostid
* Type: Integer
* Required: False
* Default: null
* Object: dcim > device
* Content types: DCIM > Device, DCIM > Virtual Chassis (optional)
```
```
* Type: Text
* Name: zabbix_template
* Type: Text
* Required: False
* Default: null
* Object: dcim > device_type
* Content types: DCIM > Device Type
```
```
* Name: primary_ip
* Type: Object
* Object type: IPAM > IP Address
* Required: False
* Default: null
* Content types: DCIM > Virtual Chassis
```
You can make the `zabbix_hostid` field hidden or read-only to prevent human intervention.
@ -191,6 +199,13 @@ See `config.py.example` for an extensive example map.
Any Zabix Inventory fields that are not included in the map will not be touched by the script,
so you can safely add manual values or use items to automatically add values to other fields.
### Clustering
When `clustering` is set to `True`, the script will monitor devices within a a Virtual Chassis through the master device.
If needed, when `clusterip` is set to `True`, the script will try to use the custom field name defined in `clusterip_cf` on the Virtual Chassis
as the primary IP for the Master device. This is only true when the master device has no primary ip set.
Setting this up allows you to use FHRP group assigned floating IPs to monitor your clusters which can be useful for a variety of vendors.
### Template source
You can either use a Netbox device type custom field or Netbox config context for the Zabbix template information.

View File

@ -15,6 +15,11 @@ device_cf = "zabbix_hostid"
## Enable clustering of devices with virtual chassis setup
clustering = False
# Allow monitoring on shared cluster IP if no primary IP has been set on the master device.
# clusterip_cf specifies which custom field contains the IP address object in the Virtual Chassis.
clusterip = False
clusterip_cf = "primary_ip"
## Enable hostgroup generation. Requires permissions in Zabbix
create_hostgroups = True

View File

@ -13,6 +13,7 @@ from modules.tools import build_path
try:
from config import (
template_cf, device_cf,
clustering, clusterip, clusterip_cf,
traverse_site_groups,
traverse_regions,
inventory_sync,
@ -31,7 +32,7 @@ class NetworkDevice():
INPUT: (Netbox device class, ZabbixAPI class, journal flag, NB journal class)
"""
def __init__(self, nb, zabbix, nb_journal_class, nb_version, journal=None, logger=None):
def __init__(self, nb, zabbix, nb_journal_class, nb_version, nb_vcs, journal=None, logger=None):
self.nb = nb
self.id = nb.id
self.name = nb.name
@ -53,18 +54,27 @@ class NetworkDevice():
self.inventory_mode = -1
self.inventory = {}
self.logger = logger if logger else getLogger(__name__)
self._setBasics()
self._setBasics(nb_vcs)
def _setBasics(self):
def _setBasics(self,nb_vcs):
"""
Sets basic information like IP address.
"""
# Return error if device does not have primary IP.
self.ip = None
if self.nb.primary_ip:
self.cidr = self.nb.primary_ip.address
self.ip = self.cidr.split("/")[0]
else:
e = f"Device {self.name}: no primary IP."
elif bool(self.nb.virtual_chassis) and clustering and clusterip:
devcluster = next((vc for vc in nb_vcs if vc['id'] == self.nb.virtual_chassis.id), None)
if (devcluster and clusterip_cf in devcluster['custom_fields'] and
devcluster['custom_fields'][clusterip_cf]):
self.ip = devcluster['custom_fields'][clusterip_cf]['address'].split("/")[0]
self.logger.debug(f"Device {self.name} is cluster member, "
f"using primary cluster ip address {self.ip}.")
if not self.ip:
# Return error if device does not have primary IP.
e = f"Device {self.name} no primary IP, skipping this device."
self.logger.info(e)
raise SyncInventoryError(e)
@ -262,7 +272,7 @@ class NetworkDevice():
raise SyncInventoryError(e)
return self.nb.virtual_chassis.master.id
def promoteMasterDevice(self):
def promoteMasterDevice(self,vcs):
"""
If device is Primary in cluster,
promote device name to the cluster name.

View File

@ -19,7 +19,8 @@ try:
zabbix_device_removal,
zabbix_device_disable,
hostgroup_format,
nb_device_filter
nb_device_filter,
device_cf
)
except ModuleNotFoundError:
print("Configuration file config.py not found in main directory."
@ -107,6 +108,7 @@ def main(arguments):
# Get all Zabbix and Netbox data
netbox_devices = netbox.dcim.devices.filter(**nb_device_filter)
netbox_site_groups = convert_recordset((netbox.dcim.site_groups.all()))
netbox_virtual_chassis = convert_recordset((netbox.dcim.virtual_chassis.all()))
netbox_regions = convert_recordset(netbox.dcim.regions.all())
netbox_journals = netbox.extras.journal_entries
zabbix_groups = zabbix.hostgroup.get(output=['groupid', 'name'])
@ -130,21 +132,44 @@ def main(arguments):
for nb_device in netbox_devices:
try:
# Set device instance set data such as hostgroup and template information.
vcobj = None
device = NetworkDevice(nb_device, zabbix, netbox_journals, nb_version,
create_journal, logger)
netbox_virtual_chassis, create_journal, logger)
device.set_hostgroup(hostgroup_format,netbox_site_groups,netbox_regions)
device.set_template(templates_config_context, templates_config_context_overrule)
device.set_inventory(nb_device)
# Checks if device is part of cluster.
# Requires clustering variable
if device.isCluster() and clustering:
vcobj = netbox.dcim.virtual_chassis.get(id=device.nb.virtual_chassis.id)
# Check if device is primary or secondary
if device.promoteMasterDevice():
if device.promoteMasterDevice(netbox_virtual_chassis):
e = (f"Device {device.name}: is "
f"part of cluster and primary.")
logger.info(e)
# If the device has a Zabbix host ID, make sure to assign it
# to the Virtual Chassis if that is not yet the case.
if device.zabbix_id and not vcobj.custom_fields[device_cf]:
logger.debug(f"Assigning Zabbix host id {device.zabbix_id}"
f" to Virtual Chassis {device.nb.virtual_chassis['name']}.")
vcobj.custom_fields[device_cf] = int(device.zabbix_id)
vcobj.save()
# If the device does not have a Zabbix host ID, use the Virtual Cluster Zabbix ID
elif vcobj.custom_fields[device_cf] and not device.zabbix_id:
logger.debug(f"Assigning Zabbix host id {vcobj.custom_fields[device_cf]}"
f" to device {device.nb.name} based on cluster membership.")
device.zabbix_id = int(vcobj.custom_fields[device_cf])
device.nb.custom_fields[device_cf] = device.zabbix_id
device.nb.save()
else:
# Device is secondary in cluster.
# if the device has a Zabbix host ID but is no longer master of the Virtual Chassis,
# make sure to cleanup the host ID.
if vcobj.custom_fields[device_cf] and device.zabbix_id:
logger.debug(f"Device {device.name} cleanup old Zabbix host ID"
"{device.zabbix_id} artefact in NetBox.")
device.nb.custom_fields[device_cf] = None
device.nb.save()
# Device is not primary in cluster.
# Don't continue with this device.
e = (f"Device {device.name}: is part of cluster "
f"but not primary. Skipping this host...")
@ -183,6 +208,12 @@ def main(arguments):
# Add device to Zabbix
device.createInZabbix(zabbix_groups, zabbix_templates,
zabbix_proxy_list)
# Make sure to save hostid in Virtual Chassis as well
if device.isCluster() and clustering and device.zabbix_id:
logger.debug(f"Assigning Zabbix host id {device.zabbix_id}"
f" to Virtual Cluster {vcobj.name}")
vcobj.custom_fields[device_cf] = int(device.zabbix_id)
vcobj.save()
except SyncError:
pass