Splitted hostgroup generation logic into its seperate module. Changed hostgroup "dev_role" to "role" for VM role prepration. Started work on basic VM class.

This commit is contained in:
TheNetworkGuy 2024-10-25 16:02:08 +02:00
parent 2e867d1129
commit 053028b283
5 changed files with 179 additions and 56 deletions

View File

@ -41,7 +41,7 @@ zabbix_device_disable = ["Offline", "Planned", "Staged", "Failed"]
# 'Global/Europe/Netherlands/Amsterdam' instead of just 'Amsterdam'. # 'Global/Europe/Netherlands/Amsterdam' instead of just 'Amsterdam'.
# #
# traverse_site_groups controls the same behaviour for any assigned site_groups. # traverse_site_groups controls the same behaviour for any assigned site_groups.
hostgroup_format = "site/manufacturer/dev_role" hostgroup_format = "site/manufacturer/role"
traverse_regions = False traverse_regions = False
traverse_site_groups = False traverse_site_groups = False

View File

@ -9,7 +9,7 @@ from zabbix_utils import APIRequestError
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
from modules.tools import build_path from modules.hostgroups import Hostgroup
try: try:
from config import ( from config import (
template_cf, device_cf, template_cf, device_cf,
@ -91,58 +91,12 @@ class NetworkDevice():
def set_hostgroup(self, hg_format, nb_site_groups, nb_regions): def set_hostgroup(self, hg_format, nb_site_groups, nb_regions):
"""Set the hostgroup for this device""" """Set the hostgroup for this device"""
# Get all variables from the NB data # Create new Hostgroup instance
dev_location = str(self.nb.location) if self.nb.location else None hg = Hostgroup("dev", self.nb, self.nb_api_version,
# Check the Netbox version. Use backwards compatibility for versions 2 and 3. traverse_site_groups, traverse_regions,
if self.nb_api_version.startswith(("2", "3")): nb_site_groups, nb_regions)
dev_role = self.nb.device_role.name # Generate hostgroup based on hostgroup format
else: self.hostgroup = hg.generate(hg_format)
dev_role = self.nb.role.name
manufacturer = self.nb.device_type.manufacturer.name
region = str(self.nb.site.region) if self.nb.site.region else None
site = self.nb.site.name
site_group = str(self.nb.site.group) if self.nb.site.group else None
tenant = str(self.tenant) if self.tenant else None
tenant_group = str(self.tenant.group) if tenant else None
# Set mapper for string -> variable
hostgroup_vars = {"dev_location": dev_location, "dev_role": dev_role,
"manufacturer": manufacturer, "region": region,
"site": site, "site_group": site_group,
"tenant": tenant, "tenant_group": tenant_group}
# Generate list based off string input format
hg_items = hg_format.split("/")
hostgroup = ""
# Go through all hostgroup items
for item in hg_items:
# Check if the variable (such as Tenant) is empty.
if not hostgroup_vars[item]:
continue
# Check if the item is a custom field name
if item not in hostgroup_vars:
cf_value = self.nb.custom_fields[item] if item in self.nb.custom_fields else None
if cf_value:
# If there is a cf match, add the value of this cf to the hostgroup
hostgroup += cf_value + "/"
# Should there not be a match, this means that
# the variable is invalid. Skip regardless.
continue
# Add value of predefined variable to hostgroup format
if item == "site_group" and nb_site_groups and traverse_site_groups:
group_path = build_path(site_group, nb_site_groups)
hostgroup += "/".join(group_path) + "/"
elif item == "region" and nb_regions and traverse_regions:
region_path = build_path(region, nb_regions)
hostgroup += "/".join(region_path) + "/"
else:
hostgroup += hostgroup_vars[item] + "/"
# If the final hostgroup variable is empty
if not hostgroup:
e = (f"{self.name} has no reliable hostgroup. This is"
"most likely due to the use of custom fields that are empty.")
self.logger.error(e)
raise SyncInventoryError(e)
# Remove final inserted "/" and set hostgroup to class var
self.hostgroup = hostgroup.rstrip("/")
def set_template(self, prefer_config_context, overrule_custom): def set_template(self, prefer_config_context, overrule_custom):
""" Set Template """ """ Set Template """

146
modules/hostgroups.py Normal file
View File

@ -0,0 +1,146 @@
"""Module for all hostgroup related code"""
from modules.exceptions import HostgroupError
from modules.tools import build_path
class Hostgroup():
"""Hostgroup class for devices and VM's
Takes type (vm or dev) and NB object"""
def __init__(self, obj_type, nb_obj, version,
nested_sitegroup_flag, nested_region_flag,
nb_groups, nb_regions):
if obj_type not in ("vm", "dev"):
raise HostgroupError(f"Unable to create hostgroup with type {type}")
self.type = str(obj_type)
self.nb = nb_obj
self.name = self.nb.name
self.nb_version = version
# Used for nested data objects
self.nested_objects = {"site_group": {"flag": nested_sitegroup_flag, "data": nb_groups},
"region": {"flag": nested_region_flag, "data": nb_regions}}
self._set_format_options()
def __str__(self):
return f"Hostgroup for {self.type} {self.name}"
def __repr__(self):
return self.__str__()
def _set_format_options(self):
"""
Set all available variables
for hostgroup generation
"""
format_options = {}
# Set variables for both type of devices
if self.type in ("vm", "dev"):
# Role fix for Netbox <=3
role = None
if self.nb_version.startswith(("2", "3")) and self.type == "dev":
role = self.nb.device_role.name
else:
role = self.nb.role.name
# Add default formatting options
# Check if a site is configured. A site is optional for VMs
format_options["region"] = None
format_options["location"] = None
format_options["site_group"] = None
if self.nb.site:
if self.nb.site.region:
format_options["region"] = self.generate_parents("region",
str(self.nb.site.region))
if self.nb.location:
format_options["location"] = str(self.nb.location)
if self.nb.site.group:
format_options["site_group"] = self.generate_parents("site_group",
str(self.nb.site.group))
format_options["role"] = role
format_options["site"] = self.nb.site.name if self.nb.site else None
format_options["tenant"] = str(self.nb.tenant) if self.nb.tenant else None
format_options["tenant_group"] = str(self.tenant.group) if self.nb.tenant else None
format_options["platform"] = self.nb.platform.name if self.nb.platform else None
# Variables only applicable for devices
if self.type == "dev":
format_options["manufacturer"] = self.nb.device_type.manufacturer.name
# Variables only applicable for VM's
if self.type == "vm":
format_options["cluster"] = str(self.nb.cluster.name) if self.nb.cluster else None
self.format_options = format_options
def generate(self, hg_format=None):
"""Generate hostgroup based on a provided format"""
# Set format to default in case its not specified
if not hg_format:
hg_format = "site/manufacturer/role" if self.type == "dev" else "cluster/role"
# Split all given names
hg_output = list()
hg_items = hg_format.split("/")
for hg_item in hg_items:
# Check if requested data is available as option for this host
if hg_item not in self.format_options:
# Check if a custom field exists with this name
cf_data = self.custom_field_lookup(hg_item)
# CF does not exist
if not cf_data["result"]:
raise HostgroupError(f"Unable to generate hostgroup for host {self.name}. "
f"Item type {hg_item} not supported.")
# CF data is populated
if cf_data["cf"]:
hg_output.append(cf_data["cf"])
continue
# Check if there is a value associated to the variable.
# For instance, if a device has no location, do not use it with hostgroup calculation
hostgroup_value = self.format_options[hg_item]
if hostgroup_value:
hg_output.append(hostgroup_value)
# Check if the hostgroup is populated with at least one item.
if bool(hg_output):
return "/".join(hg_output)
raise HostgroupError(f"Unable to generate hostgroup for host {self.name}."
"Not enough valid items. This is most likely"
"due to the use of custom fields that are empty.")
def list_formatoptions(self):
"""
Function to easily troubleshoot which values
are generated for a specific device or VM.
"""
print(f"The following options are available for host {self.name}")
for option_type, value in self.format_options.items():
if value is not None:
print(f"{option_type} - {value}")
print("The following options are not available")
for option_type, value in self.format_options.items():
if value is None:
print(f"{option_type}")
def custom_field_lookup(self, hg_category):
"""
Checks if a valid custom field is present in Netbox.
INPUT: Custom field name
OUTPUT: dictionary with 'result' and 'cf' keys.
"""
# Check if the custom field exists
if hg_category not in self.nb.custom_fields:
return {"result": False, "cf": None}
# Checks if the custom field has been populated
if not bool(self.nb.custom_fields[hg_category]):
return {"result": True, "cf": None}
# Custom field exists and is populated
return {"result": True, "cf": self.nb.custom_fields[hg_category]}
def generate_parents(self, nest_type, child_object):
"""
Generates parent objects to implement nested regions / nested site groups
INPUT: nest_type to set which type of nesting is going to be processed
child_object: the name of the child object (for instance the last NB region)
OUTPUT: STRING - Either the single child name or child and parents.
"""
# Check if this type of nesting is supported.
if not nest_type in self.nested_objects:
return child_object
# If the nested flag is True, perform parent calculation
if self.nested_objects[nest_type]["flag"]:
final_nested_object = build_path(child_object, self.nested_objects[nest_type]["data"])
return "/".join(final_nested_object)
# Nesting is not allowed for this object. Return child_object
return child_object

23
modules/virtualMachine.py Normal file
View File

@ -0,0 +1,23 @@
"""Module that hosts all functions for virtual machine processing"""
from modules.exceptions import *
class VirtualMachine():
"""Model for virtual machines"""
def __init__(self, nb, name):
self.nb = nb
self.name = name
self.hostgroup = None
def __repr__(self):
return self.name
def __str__(self):
return self.__repr__()
def _data_prep(self):
self.platform = self.nb.platform.name
self.cluster = self.nb.cluster.name
def set_hostgroup(self):
self.hostgroup = "Virtual machines"

View File

@ -76,7 +76,7 @@ def main(arguments):
netbox = api(netbox_host, token=netbox_token, threading=True) netbox = api(netbox_host, token=netbox_token, threading=True)
# Check if the provided Hostgroup layout is valid # Check if the provided Hostgroup layout is valid
hg_objects = hostgroup_format.split("/") hg_objects = hostgroup_format.split("/")
allowed_objects = ["dev_location", "dev_role", "manufacturer", "region", allowed_objects = ["dev_location", "role", "manufacturer", "region",
"site", "site_group", "tenant", "tenant_group"] "site", "site_group", "tenant", "tenant_group"]
# Create API call to get all custom fields which are on the device objects # Create API call to get all custom fields which are on the device objects
device_cfs = netbox.extras.custom_fields.filter(type="text", content_type_id=23) device_cfs = netbox.extras.custom_fields.filter(type="text", content_type_id=23)