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 ### 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): 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 * Name: zabbix_hostid
* Type: Integer
* Required: False * Required: False
* Default: null * Default: null
* Object: dcim > device * Content types: DCIM > Device, DCIM > Virtual Chassis (optional)
``` ```
``` ```
* Type: Text
* Name: zabbix_template * Name: zabbix_template
* Type: Text
* Required: False * Required: False
* Default: null * 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. 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, 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. 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 ### Template source
You can either use a Netbox device type custom field or Netbox config context for the Zabbix template information. 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 ## Enable clustering of devices with virtual chassis setup
clustering = False 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 ## Enable hostgroup generation. Requires permissions in Zabbix
create_hostgroups = True create_hostgroups = True

View File

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

View File

@ -19,7 +19,8 @@ try:
zabbix_device_removal, zabbix_device_removal,
zabbix_device_disable, zabbix_device_disable,
hostgroup_format, hostgroup_format,
nb_device_filter nb_device_filter,
device_cf
) )
except ModuleNotFoundError: except ModuleNotFoundError:
print("Configuration file config.py not found in main directory." print("Configuration file config.py not found in main directory."
@ -107,6 +108,7 @@ def main(arguments):
# Get all Zabbix and Netbox data # Get all Zabbix and Netbox data
netbox_devices = netbox.dcim.devices.filter(**nb_device_filter) netbox_devices = netbox.dcim.devices.filter(**nb_device_filter)
netbox_site_groups = convert_recordset((netbox.dcim.site_groups.all())) 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_regions = convert_recordset(netbox.dcim.regions.all())
netbox_journals = netbox.extras.journal_entries netbox_journals = netbox.extras.journal_entries
zabbix_groups = zabbix.hostgroup.get(output=['groupid', 'name']) zabbix_groups = zabbix.hostgroup.get(output=['groupid', 'name'])
@ -130,21 +132,44 @@ def main(arguments):
for nb_device in netbox_devices: for nb_device in netbox_devices:
try: try:
# Set device instance set data such as hostgroup and template information. # Set device instance set data such as hostgroup and template information.
vcobj = None
device = NetworkDevice(nb_device, zabbix, netbox_journals, nb_version, 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_hostgroup(hostgroup_format,netbox_site_groups,netbox_regions)
device.set_template(templates_config_context, templates_config_context_overrule) device.set_template(templates_config_context, templates_config_context_overrule)
device.set_inventory(nb_device) device.set_inventory(nb_device)
# Checks if device is part of cluster. # Checks if device is part of cluster.
# Requires clustering variable # Requires clustering variable
if device.isCluster() and clustering: if device.isCluster() and clustering:
vcobj = netbox.dcim.virtual_chassis.get(id=device.nb.virtual_chassis.id)
# Check if device is primary or secondary # Check if device is primary or secondary
if device.promoteMasterDevice(): if device.promoteMasterDevice(netbox_virtual_chassis):
e = (f"Device {device.name}: is " e = (f"Device {device.name}: is "
f"part of cluster and primary.") f"part of cluster and primary.")
logger.info(e) 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: 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. # Don't continue with this device.
e = (f"Device {device.name}: is part of cluster " e = (f"Device {device.name}: is part of cluster "
f"but not primary. Skipping this host...") f"but not primary. Skipping this host...")
@ -183,6 +208,12 @@ def main(arguments):
# Add device to Zabbix # Add device to Zabbix
device.createInZabbix(zabbix_groups, zabbix_templates, device.createInZabbix(zabbix_groups, zabbix_templates,
zabbix_proxy_list) 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: except SyncError:
pass pass